From 5b2f6ccccd8eda566856c054a684ba03c4e1cbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CClems=E2=80=9D?= Date: Tue, 12 Nov 2024 09:51:30 +0100 Subject: [PATCH] task#60 core improvement to improve testability --- .gitignore | 1 + README.md | 3 + application/pom.xml | 6 +- .../bank/account/BankAccountEntity.java | 71 +-- .../bank/account/BankAccountMapper.java | 13 + .../bank/account/BankAccountRepository.java | 56 +- .../bank/account/BankAccountServiceImpl.java | 44 +- .../bank/customer/CustomerEntity.java | 158 ++---- .../bank/customer/CustomerMapper.java | 7 +- .../bank/customer/CustomerRepository.java | 13 + .../bank/customer/gender/GenderConverter.java | 12 +- .../maritalstatus/MaritalStatusConverter.java | 20 +- .../bank/transaction/TransactionEntity.java | 97 +--- .../bank/transaction/TransactionMapper.java | 48 +- .../TransactionProcessorTemplate.java | 46 ++ .../transaction/TransactionRepository.java | 31 +- .../transaction/TransactionServiceImpl.java | 59 +- .../bank/transaction/TransactionUtils.java | 48 +- .../category/cash/CashAmountService.java | 58 ++ .../type/deposit/DepositProcessorFactory.java | 34 ++ .../type/deposit/DepositProcessorImpl.java | 43 ++ .../withdraw/WithdrawProcessorFactory.java | 34 ++ .../type/withdraw/WithdrawProcessorImpl.java | 44 ++ .../digital/DigitalTransactionProcessor.java | 60 +++ .../type/credit/CreditProcessorFactory.java | 33 ++ .../type/credit/CreditProcessorImpl.java | 44 ++ .../type/debit/DebitProcessorFactory.java | 31 ++ .../type/debit/DebitProcessorImpl.java | 44 ++ .../status/TransactionStatusServiceImpl.java | 18 +- .../transaction/type/CreditProcessorImpl.java | 13 - .../transaction/type/DebitProcessorImpl.java | 13 - .../type/DepositProcessorImpl.java | 13 - .../type/DigitalTransactionProcessor.java | 11 - .../PhysicalCashTransactionProcessor.java | 11 - .../type/TransactionProcessorServiceImpl.java | 192 ------- .../cdx/bas/application/config/Producers.java | 2 + .../bas/application/mapper/MappingType.java | 5 - .../bank/account/BankAccountMapperTest.java | 269 +++++----- .../account/BankAccountRepositoryTest.java | 61 ++- .../bank/account/BankAccountServiceTest.java | 449 +++++----------- .../bank/customer/CustomerMapperTest.java | 76 +-- .../bank/customer/CustomerRepositoryTest.java | 17 +- .../customer/CustomerServiceImplTest.java | 188 ++++++- .../customer/gender/GenderConverterTest.java | 12 +- .../MaritalStatusConverterTest.java | 30 +- .../transaction/TransactionMapperTest.java | 66 +-- .../TransactionRepositoryTest.java | 76 +-- .../transaction/TransactionServiceTest.java | 259 ++++----- .../TransactionStatusServiceTest.java | 101 ++-- .../transaction/TransactionUtilsTest.java | 97 ++-- .../type/CreditProcessorServiceImplTest.java | 88 +++ .../type/DebitProcessorServiceImplTest.java | 90 ++++ .../type/DepositProcessorServiceImplTest.java | 80 +++ .../TransactionProcessorServiceImplTest.java | 502 ------------------ .../WithdrawProcessorServiceImplTest.java | 80 +++ .../application/scheduler/SchedulerTest.java | 113 ++-- .../bank/account/BankAccountResource.java | 6 +- .../bank/customer/CustomerResource.java | 3 - .../bank/transaction/TransactionResource.java | 10 +- .../bank/account/BankAccountResourceTest.java | 60 ++- .../bank/customer/CustomerResourceTest.java | 4 +- .../transaction/TransactionResourceTest.java | 125 +++-- domain/pom.xml | 15 +- .../bas/domain/bank/account/BankAccount.java | 90 +++- .../bank/account/BankAccountServicePort.java | 13 - .../account/checking/CheckingBankAccount.java | 19 +- .../bank/account/mma/MMABankAccount.java | 17 +- .../account/saving/SavingBankAccount.java | 17 +- .../bas/domain/bank/customer/Customer.java | 117 ++-- .../bank/customer/CustomerException.java | 2 + .../customer/CustomerPersistencePort.java | 7 + .../domain/bank/customer/gender/Gender.java | 8 +- .../customer/maritalstatus/MaritalStatus.java | 10 +- .../domain/bank/transaction/Transaction.java | 133 ++++- .../TransactionControllerPort.java | 8 +- .../TransactionPersistencePort.java | 16 + .../transaction/TransactionServicePort.java | 12 +- .../validator => }/TransactionValidator.java | 15 +- .../{ => category}/NewCashTransaction.java | 4 +- .../{ => category}/NewDigitalTransaction.java | 4 +- .../category/cash/CashAmountServicePort.java | 5 + .../CashTransactionProcessingDetails.java | 43 ++ .../deposit/DepositAmountServiceImpl.java | 33 ++ .../withdraw/WithdrawAmountServiceImpl.java | 31 ++ .../digital/DigitalAmountServicePort.java | 5 + .../DigitalTransactionProcessingDetails.java | 53 ++ .../digital}/type/TransactionType.java | 2 +- .../digital/type}/TypeValidator.java | 5 +- .../digital/type}/ValidType.java | 3 +- .../type/credit/CreditAmountServiceImpl.java | 33 ++ .../type/debit/DebitAmountServiceImpl.java | 33 ++ .../category/group/AdvancedGroup.java | 4 + .../group/DigitalTransactionGroup.java | 2 +- .../group/ExistingTransactionGroup.java | 2 +- .../group/NewTransactionGroup.java | 2 +- .../group/PhysicalCashTransactionGroup.java | 2 +- .../validator => status}/StatusValidator.java | 5 +- .../status/TransactionStatusServicePort.java | 1 + .../validator => status}/ValidStatus.java | 4 +- .../type/TransactionProcessorServicePort.java | 34 -- .../validation/group/AdvancedGroup.java | 4 - .../currency/error/CurrencyException.java | 6 + .../currency/rate/ExchangeRateUtils.java | 6 +- .../validation/CurrencyValidator.java | 4 - .../currency/validation/ValidCurrency.java | 2 +- .../bas/domain/exception/DomainException.java | 5 + .../bas/domain/message/CommonMessages.java | 11 +- .../bas/domain/message/MessageFormatter.java | 16 +- .../domain/metadata/MetadataFieldNames.java | 11 +- .../com/cdx/bas/domain/money/AmountUtils.java | 8 +- .../cdx/bas/domain/money/AmountValidator.java | 6 +- .../com/cdx/bas/domain/testing/Generated.java | 14 + .../bank/account/BankAccountFactoryTest.java | 9 +- .../bank/transaction/TransactionTest.java | 70 +++ .../transaction/TransactionValidatorTest.java | 168 +++--- .../deposit/DepositAmountServiceImplTest.java | 77 +++ .../WithdrawAmountServiceImplTest.java | 72 +++ .../credit/CreditAmountServiceImplTest.java | 86 +++ .../debit/DebitAmountServiceImplTest.java | 86 +++ .../validation/CurrencyValidatorTest.java | 28 +- .../domain/message/MessageFormatterTest.java | 1 - .../cdx/bas/domain/money/AmountUtilsTest.java | 2 - .../bas/domain/money/AmountValidatorTest.java | 14 +- env/init-pg.sql | 2 +- env/test-resources/init-test-h2.sql | 2 +- env/test-resources/insert-test-h2.sql | 10 +- pom.xml | 4 +- 127 files changed, 3321 insertions(+), 2510 deletions(-) create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionProcessorTemplate.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/CashAmountService.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorFactory.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorImpl.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorFactory.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorImpl.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/DigitalTransactionProcessor.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorFactory.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorImpl.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorFactory.java create mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorImpl.java delete mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorImpl.java delete mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorImpl.java delete mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorImpl.java delete mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/type/DigitalTransactionProcessor.java delete mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/type/PhysicalCashTransactionProcessor.java delete mode 100644 application/src/main/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImpl.java delete mode 100644 application/src/main/java/com/cdx/bas/application/mapper/MappingType.java create mode 100644 application/src/test/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorServiceImplTest.java create mode 100644 application/src/test/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorServiceImplTest.java create mode 100644 application/src/test/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorServiceImplTest.java delete mode 100644 application/src/test/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImplTest.java create mode 100644 application/src/test/java/com/cdx/bas/application/bank/transaction/type/WithdrawProcessorServiceImplTest.java rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation/validator => }/TransactionValidator.java (87%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{ => category}/NewCashTransaction.java (71%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{ => category}/NewDigitalTransaction.java (72%) create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashAmountServicePort.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashTransactionProcessingDetails.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImpl.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImpl.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalAmountServicePort.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalTransactionProcessingDetails.java rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{ => category/digital}/type/TransactionType.java (51%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation/validator => category/digital/type}/TypeValidator.java (88%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation/validator => category/digital/type}/ValidType.java (82%) create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImpl.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImpl.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/AdvancedGroup.java rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation => category}/group/DigitalTransactionGroup.java (65%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation => category}/group/ExistingTransactionGroup.java (62%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation => category}/group/NewTransactionGroup.java (59%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation => category}/group/PhysicalCashTransactionGroup.java (67%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation/validator => status}/StatusValidator.java (86%) rename domain/src/main/java/com/cdx/bas/domain/bank/transaction/{validation/validator => status}/ValidStatus.java (69%) delete mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionProcessorServicePort.java delete mode 100644 domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/AdvancedGroup.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/testing/Generated.java create mode 100644 domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionTest.java create mode 100644 domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImplTest.java create mode 100644 domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImplTest.java create mode 100644 domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImplTest.java create mode 100644 domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImplTest.java diff --git a/.gitignore b/.gitignore index e08c7733..2be3a824 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ pom.xml.versionsBackup release.properties .flattened-pom.xml out/ +.mvn/ # Eclipse .project diff --git a/README.md b/README.md index 2fe934a4..ddd82d08 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,9 @@ quarkus dev -e *A la racine du projet* ```bash +# generate resources for test +mvn process-test-resources +# run tests mvn test ``` diff --git a/application/pom.xml b/application/pom.xml index 77abc096..87da6218 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -120,7 +120,11 @@ 21 21 - + + org.projectlombok + lombok + ${lombok.version} + io.quarkus quarkus-panache-common diff --git a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountEntity.java b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountEntity.java index 6de0acfa..e0234636 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountEntity.java +++ b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountEntity.java @@ -3,12 +3,22 @@ import com.cdx.bas.application.bank.customer.CustomerEntity; import com.cdx.bas.application.bank.transaction.TransactionEntity; import com.cdx.bas.domain.bank.account.type.AccountType; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; import java.math.BigDecimal; -import java.util.*; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +@Data +@AllArgsConstructor +@NoArgsConstructor @Entity @Table(schema = "basapp", name = "bank_accounts", uniqueConstraints = @UniqueConstraint(columnNames = "account_id")) public class BankAccountEntity extends PanacheEntityBase { @@ -26,59 +36,34 @@ public class BankAccountEntity extends PanacheEntityBase { @Column(name = "balance", nullable = false) private BigDecimal balance; - @ManyToMany(mappedBy = "accounts", fetch = FetchType.LAZY) + @ToString.Exclude + @JsonIgnore + @ManyToMany(mappedBy = "accounts", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE}) private Set customers = new HashSet<>(); @OneToMany(mappedBy = "emitterBankAccountEntity", cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @OrderBy("date") private Set issuedTransactions = new HashSet<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public BigDecimal getBalance() { - return balance; - } - - public void setBalance(BigDecimal balance) { - this.balance = balance; - } - - public Set getCustomers() { - return customers; - } - - public void setCustomers(Set customers) { - this.customers = customers; - } - - public Set getIssuedTransactions() { - return issuedTransactions; - } - - public void setIssuedTransactions(Set issuedTransactions) { - this.issuedTransactions = issuedTransactions; - } + @OneToMany(mappedBy = "receiverBankAccountEntity") + @OrderBy("date") + private Set incomingTransactions = new HashSet<>(); @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BankAccountEntity that = (BankAccountEntity) o; - return Objects.equals(id, that.id) && type == that.type && Objects.equals(balance, that.balance) && Objects.equals(customers, that.customers) && Objects.equals(issuedTransactions, that.issuedTransactions); + return Objects.equals(id, that.id) + && type == that.type + && Objects.equals(balance, that.balance) + && Objects.equals(customers, that.customers) + && Objects.equals(issuedTransactions, that.issuedTransactions) + && Objects.equals(incomingTransactions, that.incomingTransactions); + } + + @Override + public int hashCode() { + return Objects.hash(id, type, balance); } } diff --git a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountMapper.java b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountMapper.java index 15ea5771..23b292f9 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountMapper.java +++ b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountMapper.java @@ -50,6 +50,12 @@ public BankAccount toDto(BankAccountEntity entity) { .stream() .map(transactionMapper::toDto) .collect(Collectors.toSet())); + + dto.setIncomingTransactions(entity.getIncomingTransactions() + .stream() + .map(transactionMapper::toDto) + .collect(Collectors.toSet())); + return dto; } @@ -81,6 +87,13 @@ public BankAccountEntity toEntity(BankAccount dto) { newIssuedTransactions.add(newIssuedTransactionEntity); } entity.setIssuedTransactions(newIssuedTransactions); + + Set newIncomingTransactions = new HashSet<>(); + for (Transaction incomingTransaction : dto.getIncomingTransactions()) { + TransactionEntity newIssuedTransactionEntity = transactionMapper.toEntity(incomingTransaction); + newIssuedTransactions.add(newIssuedTransactionEntity); + } + entity.setIncomingTransactions(newIncomingTransactions); return entity; } } diff --git a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountRepository.java b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountRepository.java index cbb3109b..06a4971b 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountRepository.java +++ b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountRepository.java @@ -3,6 +3,10 @@ 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.customer.Customer; +import com.cdx.bas.domain.bank.customer.CustomerPersistencePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionPersistencePort; import com.cdx.bas.domain.message.MessageFormatter; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Sort; @@ -15,8 +19,11 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.REMOVED_EMITTER_ID; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.REMOVED_RECEIVER_ID; /*** * persistence implementation for BankAccount entities @@ -31,14 +38,20 @@ public class BankAccountRepository implements BankAccountPersistencePort, Panach public static final String ID_FIELD = "id"; BankAccountMapper bankAccountMapper; + CustomerPersistencePort customerRepository; + TransactionPersistencePort transactionRepository; @PersistenceContext private EntityManager entityManager; @Inject public BankAccountRepository(BankAccountMapper bankAccountMapper, + CustomerPersistencePort customerRepository, + TransactionPersistencePort transactionRepository, EntityManager entityManager) { this.bankAccountMapper = bankAccountMapper; + this.customerRepository = customerRepository; + this.transactionRepository = transactionRepository; this.entityManager = entityManager; } @@ -75,14 +88,47 @@ public BankAccount update(BankAccount bankAccount) { @Override public Optional deleteById(long id) { - Optional entityOptional = findByIdOptional(id); - if (entityOptional.isPresent()) { - BankAccountEntity entity = entityOptional.get(); - delete(entity); + Optional optionalEntity = findByIdOptional(id); + + if (optionalEntity.isPresent()) { + BankAccountEntity entity = optionalEntity.get(); + Optional removedBankAccount = deleteForEachCustomer(bankAccountMapper.toDto(entity)); + disassociateTransactions(id); + entityManager.remove(entity); logger.debug(MessageFormatter.format(BANK_ACCOUNT_CONTEXT, DELETION_ACTION, SUCCESS_STATUS, List.of(BANK_ACCOUNT_ID_DETAIL + id))); - return Optional.of(bankAccountMapper.toDto(entity)); + + return removedBankAccount; } return Optional.empty(); } + + private void disassociateTransactions(long id) { + Set emitterTransactions = transactionRepository.findTransactionsByEmitterBankAccount(id); + emitterTransactions.forEach(transaction -> { + transaction.setEmitterAccountId(null); + transaction.getMetadata().put(REMOVED_EMITTER_ID, String.valueOf(id)); + transactionRepository.update(transaction); + }); + + Set receiverTransactions = transactionRepository.findTransactionsByReceiverBankAccount(id); + receiverTransactions.forEach(transaction -> { + transaction.setReceiverAccountId(null); + transaction.getMetadata().put(REMOVED_RECEIVER_ID, String.valueOf(id)); + transactionRepository.update(transaction); + }); + entityManager.flush(); + } + + private Optional deleteForEachCustomer(BankAccount bankAccount) { + Set customers = customerRepository.findAllById(bankAccount.getCustomersId()); + customers.forEach(customer -> { + boolean removed = customer.getAccounts().removeIf(account -> account.getId().equals(bankAccount.getId())); + if (removed) { + customerRepository.update(customer); + } + }); + entityManager.flush(); + return Optional.of(bankAccount); + } } 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 02f4616e..8dcd625a 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 @@ -7,23 +7,18 @@ import com.cdx.bas.domain.bank.account.BankAccountServicePort; import com.cdx.bas.domain.bank.account.validation.BankAccountValidator; import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; import com.cdx.bas.domain.bank.transaction.TransactionServicePort; -import com.cdx.bas.domain.currency.rate.ExchangeRateUtils; -import com.cdx.bas.domain.money.Money; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigDecimal; import java.util.List; import java.util.Optional; import static com.cdx.bas.domain.message.CommonMessages.*; import static com.cdx.bas.domain.message.MessageFormatter.format; -import static com.cdx.bas.domain.money.AmountUtils.isNotPositive; @ApplicationScoped public class BankAccountServiceImpl implements BankAccountServicePort { @@ -54,6 +49,11 @@ public List getAll() { @Override @Transactional public BankAccount findBankAccount(Long bankAccountId) { + if (bankAccountId == null) { + throw new BankAccountException(format(BANK_ACCOUNT_CONTEXT, SEARCHING_ACTION, FAILED_STATUS, + Optional.of(MISSING_ID_CAUSE), List.of(BANK_ACCOUNT_ID_DETAIL + "null"))); + } + return bankAccountRepository.findById(bankAccountId) .orElseThrow(() -> new BankAccountException(format(BANK_ACCOUNT_CONTEXT, SEARCHING_ACTION, FAILED_STATUS, Optional.of(NOT_FOUND_CAUSE), List.of(BANK_ACCOUNT_ID_DETAIL + bankAccountId)))); @@ -85,38 +85,4 @@ public BankAccount updateBankAccount(BankAccount bankAccount) throws BankAccount logger.debug(format(BANK_ACCOUNT_CONTEXT, UPDATE_ACTION, SUCCESS_STATUS)); return updatedBankAccount; } - - @Override - public void creditAmountToAccounts(Transaction transaction, BankAccount emitterBankAccount, BankAccount receiverBankAccount) { - BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(transaction.getCurrency(), transaction.getAmount()); - if (isNotPositive(euroAmount)) { - throw new TransactionException(format(CREDIT_TRANSACTION_CONTEXT, CREDIT_ACTION, FAILED_STATUS, - Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), - List.of(TRANSACTION_ID_DETAIL + transaction.getId(), EURO_AMOUNT_DETAIL + euroAmount))); - } - emitterBankAccount.getBalance().minus(Money.of(euroAmount)); - receiverBankAccount.getBalance().plus(Money.of(euroAmount)); - } - - @Override - public void depositAmountToAccount(Transaction transaction, BankAccount emitterBankAccount) { - BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(transaction.getCurrency(), transaction.getAmount()); - if (isNotPositive(euroAmount)) { - throw new TransactionException(format(DEBIT_TRANSACTION_CONTEXT, DEBIT_ACTION, FAILED_STATUS, - Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), - List.of(TRANSACTION_ID_DETAIL + transaction.getId(), EURO_AMOUNT_DETAIL + euroAmount))); - } - emitterBankAccount.getBalance().plus(Money.of(euroAmount)); - } - - @Override - public void withdrawAmountToAccount(Transaction transaction, BankAccount emitterBankAccount) { - BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(transaction.getCurrency(), transaction.getAmount()); - if (isNotPositive(euroAmount)) { - throw new TransactionException(format(WITHDRAW_TRANSACTION_CONTEXT, WITHDRAW_ACTION, FAILED_STATUS, - Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), - List.of(TRANSACTION_ID_DETAIL + transaction.getId(), EURO_AMOUNT_DETAIL + euroAmount))); - } - emitterBankAccount.getBalance().minus(Money.of(euroAmount)); - } } \ No newline at end of file diff --git a/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerEntity.java b/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerEntity.java index c3178cc8..a98a47eb 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerEntity.java +++ b/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerEntity.java @@ -9,57 +9,66 @@ import io.hypersistence.utils.hibernate.type.json.JsonType; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnTransformer; import org.hibernate.annotations.Type; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +@Data +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) @Entity @Table(schema = "basapp", name = "customers", uniqueConstraints = @UniqueConstraint(columnNames = "customer_id")) public class CustomerEntity extends PanacheEntityBase { - + @Id @Column(name = "customer_id", nullable = false) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customers_customer_id_seq_gen") @SequenceGenerator(name = "customers_customer_id_seq_gen", sequenceName = "customers_customer_id_seq", allocationSize = 1, initialValue = 1) private Long id; - + @Column(name = "first_name", nullable = false) private String firstName; - + @Column(name = "last_name", nullable = false) private String lastName; - + @Column(name = "gender", nullable = false) @Convert(converter = GenderConverter.class) private Gender gender; - + @Column(name = "marital_status", nullable = false) @Convert(converter = MaritalStatusConverter.class) private MaritalStatus maritalStatus; - + @Column(name = "birthday", nullable = false) private LocalDate birthdate; - + @Column(name = "country", nullable = false) private String country; - + @Column(name = "address", nullable = false) private String address; - + @Column(name = "city", nullable = false) private String city; - + @Column(name = "email", nullable = false) private String email; @Column(name = "phone_number", nullable = false) private String phoneNumber; - + @JsonIgnore - @ManyToMany(fetch = FetchType.LAZY) + @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "bank_accounts_customers", joinColumns = @JoinColumn(name = "customer_id"), inverseJoinColumns = @JoinColumn(name = "account_id")) private List accounts = new ArrayList<>(); @@ -68,107 +77,28 @@ public class CustomerEntity extends PanacheEntityBase { @ColumnTransformer(write = "?::jsonb") private String metadata; - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public Gender getGender() { - return gender; - } - - public void setGender(Gender gender) { - this.gender = gender; - } - - public MaritalStatus getMaritalStatus() { - return maritalStatus; - } - - public void setMaritalStatus(MaritalStatus maritalStatus) { - this.maritalStatus = maritalStatus; - } - - public LocalDate getBirthdate() { - return birthdate; - } - - public void setBirthdate(LocalDate birthdate) { - this.birthdate = birthdate; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPhoneNumber() { - return phoneNumber; - } - - public void setPhoneNumber(String phoneNumber) { - this.phoneNumber = phoneNumber; - } - - public List getAccounts() { - return accounts; - } - - public void setAccounts(List accounts) { - this.accounts = accounts; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CustomerEntity that = (CustomerEntity) o; + return Objects.equals(id, that.id) + && Objects.equals(firstName, that.firstName) + && Objects.equals(lastName, that.lastName) + && gender == that.gender + && maritalStatus == that.maritalStatus + && Objects.equals(birthdate, that.birthdate) + && Objects.equals(country, that.country) + && Objects.equals(address, that.address) + && Objects.equals(city, that.city) + && Objects.equals(email, that.email) + && Objects.equals(phoneNumber, that.phoneNumber) + && Objects.equals(accounts, that.accounts) + && Objects.equals(metadata, that.metadata); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName, gender, maritalStatus, birthdate, country, address, city, email, phoneNumber, metadata); } } diff --git a/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerMapper.java b/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerMapper.java index 6bd2618d..cac8e688 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerMapper.java +++ b/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerMapper.java @@ -19,11 +19,14 @@ @RequestScoped public class CustomerMapper implements DtoEntityMapper { - @Inject BankAccountMapper bankAccountMapper; + ObjectMapper objectMapper; @Inject - ObjectMapper objectMapper; + public CustomerMapper(ObjectMapper objectMapper, BankAccountMapper bankAccountMapper) { + this.objectMapper = objectMapper; + this.bankAccountMapper = bankAccountMapper; + } @Override public Customer toDto(CustomerEntity entity) { diff --git a/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerRepository.java b/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerRepository.java index 2a363584..e007e96e 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerRepository.java +++ b/application/src/main/java/com/cdx/bas/application/bank/customer/CustomerRepository.java @@ -48,6 +48,19 @@ public Set getAll() { .collect(Collectors.toSet()); } + + @Override + public Set findAllById(Set customersId) { + List customerEntities = entityManager + .createQuery("SELECT c FROM CustomerEntity c WHERE c.id IN :ids", CustomerEntity.class) + .setParameter("ids", customersId) + .getResultList(); + + return customerEntities.stream() + .map(customerMapper::toDto) + .collect(Collectors.toSet()); + } + @Override public Optional findById(long id) { return findByIdOptional(id).map(customerMapper::toDto); diff --git a/application/src/main/java/com/cdx/bas/application/bank/customer/gender/GenderConverter.java b/application/src/main/java/com/cdx/bas/application/bank/customer/gender/GenderConverter.java index e48932e7..d67e0eb2 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/customer/gender/GenderConverter.java +++ b/application/src/main/java/com/cdx/bas/application/bank/customer/gender/GenderConverter.java @@ -18,9 +18,9 @@ public Character convertToDatabaseColumn(Gender gender) { } return switch (gender) { - case MALE -> strMale; - case FEMALE -> strFemale; - case OTHER -> strOther; + case MALE -> STR_MALE; + case FEMALE -> STR_FEMALE; + case OTHER -> STR_OTHER; }; } @@ -31,9 +31,9 @@ public Gender convertToEntityAttribute(Character genderCode) { } return switch (genderCode) { - case strMale -> MALE; - case strFemale -> FEMALE; - case strOther -> OTHER; + case STR_MALE -> MALE; + case STR_FEMALE -> FEMALE; + case STR_OTHER -> OTHER; default -> throw new IllegalStateException(ERROR_GENDER + genderCode); }; } diff --git a/application/src/main/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverter.java b/application/src/main/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverter.java index b9943254..6a0ca373 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverter.java +++ b/application/src/main/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverter.java @@ -17,11 +17,11 @@ public Character convertToDatabaseColumn(MaritalStatus maritalStatus) { return null; // Handle null maritalStatus by returning null } return switch (maritalStatus) { - case SINGLE -> strSingle; - case MARRIED -> strMarried; - case WIDOWED -> strWidowed; - case DIVORCED -> strDivorced; - case PACS -> strPacs; + case SINGLE -> STR_SINGLE; + case MARRIED -> STR_MARRIED; + case WIDOWED -> STR_WIDOWED; + case DIVORCED -> STR_DIVORCED; + case PACS -> STR_PACS; }; } @@ -31,11 +31,11 @@ public MaritalStatus convertToEntityAttribute(Character maritalCode) { return null; // Handle null maritalCode by returning null } return switch (maritalCode) { - case strSingle -> SINGLE; - case strMarried -> MARRIED; - case strWidowed -> WIDOWED; - case strDivorced -> DIVORCED; - case strPacs -> PACS; + case STR_SINGLE -> SINGLE; + case STR_MARRIED -> MARRIED; + case STR_WIDOWED -> WIDOWED; + case STR_DIVORCED -> DIVORCED; + case STR_PACS -> PACS; default -> throw new IllegalStateException(ERROR_MARITAL_STATUS + maritalCode); }; } diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionEntity.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionEntity.java index 3c83bd68..37ea78c3 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionEntity.java +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionEntity.java @@ -2,10 +2,12 @@ import com.cdx.bas.application.bank.account.BankAccountEntity; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.hypersistence.utils.hibernate.type.json.JsonStringType; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import jakarta.persistence.*; +import lombok.*; import org.hibernate.annotations.ColumnTransformer; import org.hibernate.annotations.Type; @@ -13,6 +15,9 @@ import java.time.Instant; import java.util.Objects; +@Data +@AllArgsConstructor +@NoArgsConstructor @Entity @Table(schema = "basapp", name = "transactions", uniqueConstraints = @UniqueConstraint(columnNames = "transaction_id")) @NamedQueries(@NamedQuery( @@ -25,10 +30,14 @@ public class TransactionEntity extends PanacheEntityBase { @SequenceGenerator(name = "transactions_transaction_id_seq_gen", sequenceName = "transactions_transaction_id_seq", allocationSize = 1, initialValue = 1) private Long id; + @ToString.Exclude + @JsonIgnore @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "emitter_account_id") private BankAccountEntity emitterBankAccountEntity; + @ToString.Exclude + @JsonIgnore @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "receiver_account_id") private BankAccountEntity receiverBankAccountEntity; @@ -58,97 +67,17 @@ public class TransactionEntity extends PanacheEntityBase { @ColumnTransformer(write = "?::jsonb") private String metadata; - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public BankAccountEntity getEmitterBankAccountEntity() { - return emitterBankAccountEntity; - } - - public void setEmitterBankAccountEntity(BankAccountEntity emitterBankAccountEntity) { - this.emitterBankAccountEntity = emitterBankAccountEntity; - } - - public BankAccountEntity getReceiverBankAccountEntity() { - return receiverBankAccountEntity; - } - - public void setReceiverBankAccountEntity(BankAccountEntity receiverBankAccountEntity) { - this.receiverBankAccountEntity = receiverBankAccountEntity; - } - - public BigDecimal getAmount() { - return amount; - } - - public void setAmount(BigDecimal amount) { - this.amount = amount; - } - - public String getCurrency() { - return currency; - } - - public void setCurrency(String currency) { - this.currency = currency; - } - - public TransactionType getType() { - return type; - } - - public void setType(TransactionType type) { - this.type = type; - } - - public TransactionStatus getStatus() { - return status; - } - - public void setStatus(TransactionStatus status) { - this.status = status; - } - - public Instant getDate() { - return date; - } - - public void setDate(Instant date) { - this.date = date; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TransactionEntity that = (TransactionEntity) o; - return Objects.equals(id, that.id) - && Objects.equals(emitterBankAccountEntity, that.emitterBankAccountEntity) + return Objects.equals(id, that.id) && Objects.equals(emitterBankAccountEntity, that.emitterBankAccountEntity) && Objects.equals(receiverBankAccountEntity, that.receiverBankAccountEntity) && Objects.equals(amount, that.amount) && Objects.equals(currency, that.currency) - & type == that.type && status == that.status + && type == that.type + && status == that.status && Objects.equals(date, that.date) && Objects.equals(label, that.label) && Objects.equals(metadata, that.metadata); diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionMapper.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionMapper.java index 4556c5c0..409cdf8d 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionMapper.java +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionMapper.java @@ -20,29 +20,30 @@ @RequestScoped public class TransactionMapper implements DtoEntityMapper { - @Inject - TransactionRepository transactionRepository; + public static final String EMPTY_JSON = "{}"; - @Inject + TransactionRepository transactionRepository; BankAccountRepository bankAccountRepository; + ObjectMapper objectMapper; @Inject - ObjectMapper objectMapper; + public TransactionMapper(TransactionRepository transactionRepository, BankAccountRepository bankAccountRepository, ObjectMapper objectMapper) { + this.transactionRepository = transactionRepository; + this.bankAccountRepository = bankAccountRepository; + this.objectMapper = objectMapper; + } public Transaction toDto(TransactionEntity entity) { - Transaction dto = Transaction.builder() - .id(entity.getId()) - .type(entity.getType()) - .currency(entity.getCurrency()) - .status(entity.getStatus()) - .date(entity.getDate()) - .label(entity.getLabel()) - .build(); + Transaction dto = new Transaction(); + dto.setId(entity.getId()); + dto.setType(entity.getType()); + dto.setCurrency(entity.getCurrency()); + dto.setStatus(entity.getStatus()); + dto.setDate(entity.getDate()); + dto.setLabel(entity.getLabel()); if (entity.getEmitterBankAccountEntity() != null) { dto.setEmitterAccountId(entity.getEmitterBankAccountEntity().getId()); - } else { - throw new NoSuchElementException("Transaction does not have emitter bank account."); } if (entity.getReceiverBankAccountEntity() != null) { @@ -55,8 +56,7 @@ public Transaction toDto(TransactionEntity entity) { try { if (entity.getMetadata() != null) { - dto.setMetadata(objectMapper.readValue(entity.getMetadata(), new TypeReference>() { - })); + dto.setMetadata(objectMapper.readValue(entity.getMetadata(), new TypeReference>() {})); } else { dto.setMetadata(new HashMap<>()); } @@ -75,14 +75,20 @@ public TransactionEntity toEntity(Transaction dto) { entity.setId(dto.getId()); } - BankAccountEntity emitterBankAccountEntity = bankAccountRepository.findByIdOptional(dto.getEmitterAccountId()) - .orElseThrow(() -> new NoSuchElementException("Transaction does not have emitter bank account entity.")); - entity.setEmitterBankAccountEntity(emitterBankAccountEntity); + if (dto.getEmitterAccountId() != null) { + BankAccountEntity emitterBankAccountEntity = bankAccountRepository.findByIdOptional(dto.getEmitterAccountId()) + .orElseThrow(() -> new NoSuchElementException("Transaction does not have emitter bank account entity.")); + entity.setEmitterBankAccountEntity(emitterBankAccountEntity); + } else { + entity.setEmitterBankAccountEntity(null); + } if (dto.getReceiverAccountId() != null) { BankAccountEntity receiverBankAccountEntity = bankAccountRepository.findByIdOptional(dto.getReceiverAccountId()) .orElseThrow(() -> new NoSuchElementException("Transaction does not have receiver bank account entity.")); entity.setReceiverBankAccountEntity(receiverBankAccountEntity); + } else { + entity.setReceiverBankAccountEntity(null); } entity.setAmount(dto.getAmount()); @@ -93,10 +99,10 @@ public TransactionEntity toEntity(Transaction dto) { entity.setLabel(dto.getLabel()); try { - if (!dto.getMetadata().isEmpty()) { + if (dto.getMetadata() != null && !dto.getMetadata().isEmpty()) { entity.setMetadata(objectMapper.writeValueAsString(dto.getMetadata())); } else { - entity.setMetadata(null); + entity.setMetadata(EMPTY_JSON); } } catch (JsonProcessingException exception) { throw new MappingException(MessageFormatter.format(TRANSACTION_CONTEXT, MAP_PARSE_METADATA, FAILED_STATUS), exception); diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionProcessorTemplate.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionProcessorTemplate.java new file mode 100644 index 00000000..560753df --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionProcessorTemplate.java @@ -0,0 +1,46 @@ +package com.cdx.bas.application.bank.transaction; + +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; +import com.cdx.bas.domain.exception.DomainException; +import jakarta.transaction.Transactional; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.*; +import static com.cdx.bas.domain.message.CommonMessages.FAILED_STATUS; +import static com.cdx.bas.domain.message.CommonMessages.REFUSED_STATUS; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.ERROR_KEY; + +@NoArgsConstructor +public abstract class TransactionProcessorTemplate { + + protected abstract Transaction processCategory(Transaction transaction, Map metadata); + protected abstract void persist(Transaction transaction); + protected abstract String formatError(Transaction transaction, String errorStatus, Exception exception); + + @Transactional + public Transaction processTransaction(Transaction transaction) { + Map metadata = new HashMap<>(); + TransactionStatus transactionStatus = COMPLETED; + try { + return processCategory(transaction, metadata); + } catch (NoSuchElementException exception) { + metadata = Map.of(ERROR_KEY, exception.getMessage()); + transactionStatus = ERROR; + throw new TransactionException(formatError(transaction, FAILED_STATUS, exception)); + } catch (DomainException exception) { + metadata = Map.of(ERROR_KEY, exception.getMessage()); + transactionStatus = REFUSED; + throw new TransactionException(formatError(transaction, REFUSED_STATUS, exception)); + } finally { + transaction.setStatus(transactionStatus); + transaction.getMetadata().putAll(metadata); + persist(transaction); + } + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionRepository.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionRepository.java index fd9d261d..6a25c7ac 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionRepository.java +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionRepository.java @@ -11,7 +11,6 @@ import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import jakarta.transaction.Transactional; import org.jboss.logging.Logger; import java.util.*; @@ -48,6 +47,28 @@ public Optional findById(long id) { return findByIdOptional(id).map(transactionMapper::toDto); } + @Override + public Set findTransactionsByEmitterBankAccount(long emitterBankAccountId) { + List transactionEntities = entityManager + .createQuery("SELECT t FROM TransactionEntity t WHERE t.emitterBankAccountEntity.id = :id", TransactionEntity.class) + .setParameter("id", emitterBankAccountId) + .getResultList(); + return transactionEntities.stream() + .map(transactionMapper::toDto) + .collect(Collectors.toSet()); + } + + @Override + public Set findTransactionsByReceiverBankAccount(long receiverBankAccountId) { + List transactionEntities = entityManager + .createQuery("SELECT t FROM TransactionEntity t WHERE t.receiverBankAccountEntity.id = :id", TransactionEntity.class) + .setParameter("id", receiverBankAccountId) + .getResultList(); + return transactionEntities.stream() + .map(transactionMapper::toDto) + .collect(Collectors.toSet()); + } + @Override public Set getAll() { return findAll(Sort.by(STATUS)).stream() @@ -57,7 +78,6 @@ public Set getAll() { @Override - @Transactional public Set findAllByStatus(TransactionStatus transactionStatus) { return findAll(Sort.by(STATUS)).stream() .filter(transaction -> transaction.getStatus().equals(transactionStatus)) @@ -78,16 +98,19 @@ public Queue findUnprocessedTransactions() { public void create(Transaction transaction) { entityManager.persist(transactionMapper.toEntity(transaction)); logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, CREATION_ACTION, SUCCESS_STATUS, List.of(TRANSACTION_ID_DETAIL + transaction.getId()))); - } @Override public Transaction update(Transaction transaction) { - Transaction updatedTransaction = transactionMapper.toDto(entityManager.merge(transactionMapper.toEntity(transaction))); + TransactionEntity entity = transactionMapper.toEntity(transaction); + TransactionEntity merge = entityManager.merge(entity); + Transaction updatedTransaction = transactionMapper.toDto(merge); logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, UPDATE_ACTION, SUCCESS_STATUS, List.of(TRANSACTION_ID_DETAIL + transaction.getId()))); return updatedTransaction; } + + @Override public Optional deleteById(long id) { Optional entityOptional = findByIdOptional(id); diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionServiceImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionServiceImpl.java index 6e638116..f9755545 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionServiceImpl.java +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionServiceImpl.java @@ -1,37 +1,50 @@ package com.cdx.bas.application.bank.transaction; +import com.cdx.bas.application.bank.transaction.category.cash.type.deposit.DepositProcessorImpl; +import com.cdx.bas.application.bank.transaction.category.cash.type.withdraw.WithdrawProcessorImpl; +import com.cdx.bas.application.bank.transaction.category.digital.type.credit.CreditProcessorImpl; +import com.cdx.bas.application.bank.transaction.category.digital.type.debit.DebitProcessorImpl; import com.cdx.bas.domain.bank.transaction.*; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionProcessorServicePort; -import com.cdx.bas.domain.bank.transaction.validation.validator.TransactionValidator; import com.cdx.bas.domain.message.CommonMessages; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; -import java.util.Map; +import java.util.List; +import java.util.Optional; import java.util.Set; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.*; -import static com.cdx.bas.domain.message.CommonMessages.DEPOSIT_DETAIL; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.*; +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.message.MessageFormatter.format; @ApplicationScoped public class TransactionServiceImpl implements TransactionServicePort { private final TransactionPersistencePort transactionRepository; - private final TransactionValidator transactionValidator; - - private final TransactionProcessorServicePort transactionTypeProcessingService; + private final CreditProcessorImpl creditProcessorService; + private final DebitProcessorImpl debitProcessorService; + private final DepositProcessorImpl depositProcessorService; + private final WithdrawProcessorImpl withdrawProcessorService; @Inject public TransactionServiceImpl(TransactionPersistencePort transactionRepository, TransactionValidator transactionValidator, - TransactionProcessorServicePort transactionTypeProcessingService) { + CreditProcessorImpl creditProcessorService, + DebitProcessorImpl debitProcessorService, + DepositProcessorImpl depositProcessorService, + WithdrawProcessorImpl withdrawProcessorService) { this.transactionRepository = transactionRepository; this.transactionValidator = transactionValidator; - this.transactionTypeProcessingService = transactionTypeProcessingService; + this.creditProcessorService = creditProcessorService; + this.debitProcessorService = debitProcessorService; + this.depositProcessorService = depositProcessorService; + this.withdrawProcessorService = withdrawProcessorService; } @Override @@ -41,16 +54,12 @@ public Set getAll() { } @Override - @Transactional(value = Transactional.TxType.REQUIRES_NEW) - public void create(Transaction transaction, Map metadata) { - transaction.setMetadata(metadata); + public void create(Transaction transaction) { transactionRepository.create(transaction); } @Override - @Transactional(value = Transactional.TxType.REQUIRES_NEW) - public void update(Transaction transaction, Map metadata) { - transaction.getMetadata().putAll(metadata); + public void update(Transaction transaction) { transactionRepository.update(transaction); } @@ -72,17 +81,23 @@ public void createDigitalTransaction(NewDigitalTransaction newDigitalTransaction @Override @Transactional public Transaction findTransaction(Long transactionId) { - Transaction transaction = transactionRepository.findById(transactionId).orElse(null); - return transaction; + if (transactionId == null) { + throw new TransactionException(format(BANK_ACCOUNT_CONTEXT, SEARCHING_ACTION, FAILED_STATUS, + Optional.of(MISSING_ID_CAUSE), List.of(BANK_ACCOUNT_ID_DETAIL + "null"))); + } + + return transactionRepository.findById(transactionId) + .orElseThrow(() -> new TransactionException(format(TRANSACTION_CONTEXT, SEARCHING_ACTION, FAILED_STATUS, + Optional.of(NOT_FOUND_CAUSE), List.of(TRANSACTION_ID_DETAIL + transactionId)))); } @Override @Transactional public void processDigitalTransaction(Transaction digitalTransaction) { if (CREDIT.equals(digitalTransaction.getType())) { - transactionTypeProcessingService.credit(digitalTransaction); + creditProcessorService.processTransaction(digitalTransaction); } else if (DEBIT.equals(digitalTransaction.getType())) { - transactionTypeProcessingService.debit(digitalTransaction); + debitProcessorService.processTransaction(digitalTransaction); } } @@ -92,7 +107,7 @@ public void deposit(NewCashTransaction newDepositTransaction) { Transaction depositTransaction = TransactionUtils.getNewCashTransaction(newDepositTransaction); depositTransaction.setType(DEPOSIT); depositTransaction.setLabel(DEPOSIT_DETAIL + newDepositTransaction.amount() + StringUtils.SPACE + newDepositTransaction.currency()); - transactionTypeProcessingService.deposit(depositTransaction); + depositProcessorService.processTransaction(depositTransaction); } @Override @@ -101,6 +116,6 @@ public void withdraw(NewCashTransaction newWithdrawTransaction) { Transaction withdrawTransaction = TransactionUtils.getNewCashTransaction(newWithdrawTransaction); withdrawTransaction.setType(WITHDRAW); withdrawTransaction.setLabel(CommonMessages.WITHDRAW_DETAIL + newWithdrawTransaction.amount() + StringUtils.SPACE + newWithdrawTransaction.currency()); - transactionTypeProcessingService.withdraw(withdrawTransaction); + withdrawProcessorService.processTransaction(withdrawTransaction); } } diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionUtils.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionUtils.java index 0a45fd71..f24b5ef2 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionUtils.java +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/TransactionUtils.java @@ -1,42 +1,44 @@ package com.cdx.bas.application.bank.transaction; -import com.cdx.bas.domain.bank.transaction.NewCashTransaction; -import com.cdx.bas.domain.bank.transaction.NewDigitalTransaction; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import lombok.experimental.UtilityClass; import java.time.Clock; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; - @UtilityClass public class TransactionUtils { private static final Clock clock = Clock.systemDefaultZone(); public static Transaction getNewDigitalTransaction(NewDigitalTransaction newDigitalTransaction) { - return Transaction.builder() - .emitterAccountId(newDigitalTransaction.emitterAccountId()) - .receiverAccountId(newDigitalTransaction.receiverAccountId()) - .amount(newDigitalTransaction.amount()) - .currency(newDigitalTransaction.currency()) - .type(newDigitalTransaction.type()) - .status(UNPROCESSED) - .date(clock.instant()) - .label(newDigitalTransaction.label()) - .metadata(newDigitalTransaction.metadata()) - .build(); + Transaction transaction = new Transaction(); + transaction.setEmitterAccountId(newDigitalTransaction.emitterAccountId()); + transaction.setReceiverAccountId(newDigitalTransaction.receiverAccountId()); + transaction.setAmount(newDigitalTransaction.amount()); + transaction.setCurrency(newDigitalTransaction.currency()); + transaction.setType(newDigitalTransaction.type()); + transaction.setStatus(TransactionStatus.UNPROCESSED); // Assuming UNPROCESSED is an enum constant + transaction.setDate(clock.instant()); + transaction.setLabel(newDigitalTransaction.label()); + transaction.setMetadata(newDigitalTransaction.metadata()); + return transaction; + } public static Transaction getNewCashTransaction(NewCashTransaction newCashTransaction) { - return Transaction.builder() - .emitterAccountId(newCashTransaction.emitterAccountId()) - .amount(newCashTransaction.amount()) - .currency(newCashTransaction.currency()) - .metadata(newCashTransaction.metadata()) - .date(clock.instant()) - .status(UNPROCESSED) - .build(); + Transaction transaction = new Transaction(); + transaction.setEmitterAccountId(newCashTransaction.emitterAccountId()); + transaction.setAmount(newCashTransaction.amount()); + transaction.setCurrency(newCashTransaction.currency()); + if (newCashTransaction.metadata() != null) { + transaction.setMetadata(newCashTransaction.metadata()); + } + transaction.setDate(clock.instant()); + transaction.setStatus(TransactionStatus.UNPROCESSED); // Assuming UNPROCESSED is an enum constant + return transaction; } public static Transaction mergeTransactions(Transaction oldTransaction, Transaction newTransaction) { diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/CashAmountService.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/CashAmountService.java new file mode 100644 index 00000000..e1867995 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/CashAmountService.java @@ -0,0 +1,58 @@ +package com.cdx.bas.application.bank.transaction.category.cash; + +import com.cdx.bas.application.bank.transaction.TransactionProcessorTemplate; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import com.cdx.bas.domain.message.MessageFormatter; +import lombok.AllArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.COMPLETED; +import static com.cdx.bas.domain.message.CommonMessages.*; + +@AllArgsConstructor +public abstract class CashAmountService extends TransactionProcessorTemplate { + + private static final Logger logger = LoggerFactory.getLogger(CashAmountService.class); + + private final TransactionValidator transactionValidator; + private final TransactionStatusServicePort transactionStatusService; + private final TransactionServicePort transactionService; + private final BankAccountServicePort bankAccountService; + + protected abstract Transaction processType(CashTransactionProcessingDetails cashTransactionProcessingDetails); + protected abstract String formatError(Transaction transaction, String errorStatus, Exception exception); + + @Override + protected Transaction processCategory(Transaction transaction, Map metadata) { + transactionValidator.validateCashTransaction(transaction); + BankAccount emitterBankAccount = bankAccountService.findBankAccount(transaction.getEmitterAccountId()); + + CashTransactionProcessingDetails cashTransactionProcessingDetails = new CashTransactionProcessingDetails(transaction, emitterBankAccount, metadata); + processType(cashTransactionProcessingDetails); + + Transaction completedTransaction = transactionStatusService.setStatus(transaction, COMPLETED, metadata); + bankAccountService.updateBankAccount(emitterBankAccount); + + logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, CASH_TRANSACTION_ACTION, COMPLETED_STATUS, + List.of(TRANSACTION_ID_DETAIL + transaction.getId(), + CREDIT_DETAIL + transaction.getAmount(), + EMITTER_BANK_ACCOUNT_DETAIL + transaction.getEmitterAccountId(), + RECEIVER_BANK_ACCOUNT_DETAIL + transaction.getReceiverAccountId()))); + return completedTransaction; + } + + @Override + protected void persist(Transaction transaction) { + transactionService.create(transaction); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorFactory.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorFactory.java new file mode 100644 index 00000000..f12d6c2d --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorFactory.java @@ -0,0 +1,34 @@ +package com.cdx.bas.application.bank.transaction.category.cash.type.deposit; + +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.cash.type.deposit.DepositAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +@ApplicationScoped +public class DepositProcessorFactory { + private final TransactionValidator transactionValidator; + private final TransactionStatusServicePort transactionStatusService; + private final TransactionServicePort transactionService; + private final BankAccountServicePort bankAccountService; + private final DepositAmountServiceImpl depositAmountService; + + public DepositProcessorFactory(TransactionValidator transactionValidator, + TransactionStatusServicePort transactionStatusService, + TransactionServicePort transactionService, + BankAccountServicePort bankAccountService, DepositAmountServiceImpl depositAmountService) { + this.transactionValidator = transactionValidator; + this.transactionStatusService = transactionStatusService; + this.transactionService = transactionService; + this.bankAccountService = bankAccountService; + this.depositAmountService = depositAmountService; + } + + @Produces + public DepositProcessorImpl createCreditProcessorService() { + return new DepositProcessorImpl(transactionValidator, transactionStatusService, transactionService, bankAccountService, depositAmountService); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorImpl.java new file mode 100644 index 00000000..80895b0b --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/deposit/DepositProcessorImpl.java @@ -0,0 +1,43 @@ +package com.cdx.bas.application.bank.transaction.category.cash.type.deposit; + +import com.cdx.bas.application.bank.transaction.category.cash.CashAmountService; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.cash.type.deposit.DepositAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import com.cdx.bas.domain.message.MessageFormatter; + +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.EMITTER_AMOUNT_AFTER_KEY; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.EMITTER_AMOUNT_BEFORE_KEY; + +public class DepositProcessorImpl extends CashAmountService { + private final DepositAmountServiceImpl depositAmountService; + + public DepositProcessorImpl(TransactionValidator transactionValidator, TransactionStatusServicePort transactionStatusService, TransactionServicePort transactionService, BankAccountServicePort bankAccountService, DepositAmountServiceImpl depositAmountService) { + super(transactionValidator, transactionStatusService, transactionService, bankAccountService); + this.depositAmountService = depositAmountService; + } + + @Override + protected Transaction processType(CashTransactionProcessingDetails cashTransactionProcessingDetails) { + cashTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_BEFORE_KEY, cashTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + depositAmountService.applyToAccount(cashTransactionProcessingDetails); + cashTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_AFTER_KEY, cashTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + return cashTransactionProcessingDetails.getTransaction(); + } + + @Override + protected String formatError(Transaction transaction, String errorStatus, Exception exception) { + return MessageFormatter.format(DEPOSIT_TRANSACTION_CONTEXT, DEPOSIT_ACTION, errorStatus, + Optional.of(DOMAIN_ERROR), + List.of(TRANSACTION_ID_DETAIL + transaction.getId(), + ERROR_DETAIL + exception.getMessage())); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorFactory.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorFactory.java new file mode 100644 index 00000000..956d125f --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorFactory.java @@ -0,0 +1,34 @@ +package com.cdx.bas.application.bank.transaction.category.cash.type.withdraw; + +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.cash.type.withdraw.WithdrawAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +@ApplicationScoped +public class WithdrawProcessorFactory { + private final TransactionValidator transactionValidator; + private final TransactionStatusServicePort transactionStatusService; + private final TransactionServicePort transactionService; + private final BankAccountServicePort bankAccountService; + private final WithdrawAmountServiceImpl withdrawAmountService; + + public WithdrawProcessorFactory(TransactionValidator transactionValidator, + TransactionStatusServicePort transactionStatusService, + TransactionServicePort transactionService, + BankAccountServicePort bankAccountService, WithdrawAmountServiceImpl withdrawAmountService) { + this.transactionValidator = transactionValidator; + this.transactionStatusService = transactionStatusService; + this.transactionService = transactionService; + this.bankAccountService = bankAccountService; + this.withdrawAmountService = withdrawAmountService; + } + + @Produces + public WithdrawProcessorImpl createCreditProcessorService() { + return new WithdrawProcessorImpl(transactionValidator, transactionStatusService, transactionService, bankAccountService, withdrawAmountService); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorImpl.java new file mode 100644 index 00000000..149ea521 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/cash/type/withdraw/WithdrawProcessorImpl.java @@ -0,0 +1,44 @@ +package com.cdx.bas.application.bank.transaction.category.cash.type.withdraw; + +import com.cdx.bas.application.bank.transaction.category.cash.CashAmountService; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.cash.type.withdraw.WithdrawAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import com.cdx.bas.domain.message.MessageFormatter; + +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.EMITTER_AMOUNT_AFTER_KEY; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.EMITTER_AMOUNT_BEFORE_KEY; + +public class WithdrawProcessorImpl extends CashAmountService { + + private final WithdrawAmountServiceImpl withdrawAmountService; + + public WithdrawProcessorImpl(TransactionValidator transactionValidator, TransactionStatusServicePort transactionStatusService, TransactionServicePort transactionService, BankAccountServicePort bankAccountService, WithdrawAmountServiceImpl withdrawAmountService) { + super(transactionValidator, transactionStatusService, transactionService, bankAccountService); + this.withdrawAmountService = withdrawAmountService; + } + + @Override + protected Transaction processType(CashTransactionProcessingDetails cashTransactionProcessingDetails) { + cashTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_BEFORE_KEY, cashTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + withdrawAmountService.applyToAccount(cashTransactionProcessingDetails); + cashTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_AFTER_KEY, cashTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + return cashTransactionProcessingDetails.getTransaction(); + } + + @Override + protected String formatError(Transaction transaction, String errorStatus, Exception exception) { + return MessageFormatter.format(WITHDRAW_TRANSACTION_CONTEXT, WITHDRAW_ACTION, errorStatus, + Optional.of(DOMAIN_ERROR), + List.of(TRANSACTION_ID_DETAIL + transaction.getId(), + ERROR_DETAIL + exception.getMessage())); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/DigitalTransactionProcessor.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/DigitalTransactionProcessor.java new file mode 100644 index 00000000..a2743c85 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/DigitalTransactionProcessor.java @@ -0,0 +1,60 @@ +package com.cdx.bas.application.bank.transaction.category.digital; + +import com.cdx.bas.application.bank.transaction.TransactionProcessorTemplate; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import com.cdx.bas.domain.message.MessageFormatter; +import lombok.AllArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.COMPLETED; +import static com.cdx.bas.domain.message.CommonMessages.*; + +@AllArgsConstructor +public abstract class DigitalTransactionProcessor extends TransactionProcessorTemplate { + + private static final Logger logger = LoggerFactory.getLogger(DigitalTransactionProcessor.class); + + private final TransactionStatusServicePort transactionStatusService; + private final TransactionServicePort transactionService; + private final BankAccountServicePort bankAccountService; + + protected abstract Transaction processType(DigitalTransactionProcessingDetails digitalTransactionProcessingDetails); + protected abstract String formatError(Transaction transaction, String errorStatus, Exception exception); + + @Override + protected Transaction processCategory(Transaction transaction, Map metadata) { + BankAccount emitterBankAccount = bankAccountService.findBankAccount(transaction.getEmitterAccountId()); + BankAccount receiverBankAccount = bankAccountService.findBankAccount(transaction.getReceiverAccountId()); + Transaction currentTransaction = transactionStatusService.setAsOutstanding(transaction); + + DigitalTransactionProcessingDetails digitalTransactionProcessingDetails = new DigitalTransactionProcessingDetails(currentTransaction, + emitterBankAccount, + receiverBankAccount, + metadata); + processType(digitalTransactionProcessingDetails); + + Transaction completedTransaction = transactionStatusService.setStatus(currentTransaction, COMPLETED, metadata); + bankAccountService.updateBankAccount(emitterBankAccount); + bankAccountService.updateBankAccount(receiverBankAccount); + + logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, CREDIT_ACTION, COMPLETED_STATUS, + List.of(CREDIT_DETAIL + currentTransaction.getAmount(), + EMITTER_BANK_ACCOUNT_DETAIL + currentTransaction.getEmitterAccountId(), + RECEIVER_BANK_ACCOUNT_DETAIL + currentTransaction.getReceiverAccountId()))); + return completedTransaction; + } + + @Override + protected void persist(Transaction transaction) { + transactionService.update(transaction); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorFactory.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorFactory.java new file mode 100644 index 00000000..39946845 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorFactory.java @@ -0,0 +1,33 @@ +package com.cdx.bas.application.bank.transaction.category.digital.type.credit; + +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.type.credit.CreditAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +@ApplicationScoped +public class CreditProcessorFactory { + private final TransactionStatusServicePort transactionStatusService; + private final TransactionServicePort transactionService; + private final BankAccountServicePort bankAccountService; + private final CreditAmountServiceImpl creditAmountService; + + @Inject + public CreditProcessorFactory(TransactionStatusServicePort transactionStatusService, + TransactionServicePort transactionService, + BankAccountServicePort bankAccountService, + CreditAmountServiceImpl creditAmountService) { + this.transactionStatusService = transactionStatusService; + this.transactionService = transactionService; + this.bankAccountService = bankAccountService; + this.creditAmountService = creditAmountService; + } + + @Produces + public CreditProcessorImpl createCreditProcessor() { + return new CreditProcessorImpl(transactionStatusService, transactionService, bankAccountService, creditAmountService); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorImpl.java new file mode 100644 index 00000000..128ccc08 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/credit/CreditProcessorImpl.java @@ -0,0 +1,44 @@ +package com.cdx.bas.application.bank.transaction.category.digital.type.credit; + +import com.cdx.bas.application.bank.transaction.category.digital.DigitalTransactionProcessor; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.digital.type.credit.CreditAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import com.cdx.bas.domain.message.MessageFormatter; + +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.*; + +public class CreditProcessorImpl extends DigitalTransactionProcessor { + + private final CreditAmountServiceImpl creditAmountService; + + public CreditProcessorImpl(TransactionStatusServicePort transactionStatusService, TransactionServicePort transactionService, BankAccountServicePort bankAccountService, CreditAmountServiceImpl creditAmountService) { + super(transactionStatusService, transactionService, bankAccountService); + this.creditAmountService = creditAmountService; + } + + @Override + protected Transaction processType(DigitalTransactionProcessingDetails digitalTransactionProcessingDetails) { + digitalTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_BEFORE_KEY, digitalTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + digitalTransactionProcessingDetails.getMetadata().put(RECEIVER_AMOUNT_BEFORE_KEY, digitalTransactionProcessingDetails.getReceiverBankAccount().getBalance().getAmount().toString()); + creditAmountService.transferBetweenAccounts(digitalTransactionProcessingDetails); + digitalTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_AFTER_KEY, digitalTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + digitalTransactionProcessingDetails.getMetadata().put(RECEIVER_AMOUNT_AFTER_KEY, digitalTransactionProcessingDetails.getReceiverBankAccount().getBalance().getAmount().toString()); + return digitalTransactionProcessingDetails.getTransaction(); + } + + @Override + protected String formatError(Transaction transaction, String errorStatus, Exception exception) { + return MessageFormatter.format(CREDIT_TRANSACTION_CONTEXT, CREDIT_ACTION, errorStatus, + Optional.of(TRANSACTION_ERROR_CAUSE), + List.of(TRANSACTION_ID_DETAIL + transaction.getId(), + ERROR_DETAIL + exception.getMessage())); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorFactory.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorFactory.java new file mode 100644 index 00000000..568abd40 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorFactory.java @@ -0,0 +1,31 @@ +package com.cdx.bas.application.bank.transaction.category.digital.type.debit; + +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.type.debit.DebitAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +@ApplicationScoped +public class DebitProcessorFactory { + private final TransactionStatusServicePort transactionStatusService; + private final TransactionServicePort transactionService; + private final BankAccountServicePort bankAccountService; + private final DebitAmountServiceImpl debitAmountService; + + public DebitProcessorFactory(TransactionStatusServicePort transactionStatusService, + TransactionServicePort transactionService, + BankAccountServicePort bankAccountService, + DebitAmountServiceImpl debitAmountService) { + this.transactionStatusService = transactionStatusService; + this.transactionService = transactionService; + this.bankAccountService = bankAccountService; + this.debitAmountService = debitAmountService; + } + + @Produces + public DebitProcessorImpl createCreditProcessor() { + return new DebitProcessorImpl(transactionStatusService, transactionService, bankAccountService, debitAmountService); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorImpl.java new file mode 100644 index 00000000..ab39b333 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/category/digital/type/debit/DebitProcessorImpl.java @@ -0,0 +1,44 @@ +package com.cdx.bas.application.bank.transaction.category.digital.type.debit; + +import com.cdx.bas.application.bank.transaction.category.digital.DigitalTransactionProcessor; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.digital.type.debit.DebitAmountServiceImpl; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; +import com.cdx.bas.domain.message.MessageFormatter; + +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.*; + +public class DebitProcessorImpl extends DigitalTransactionProcessor { + + private final DebitAmountServiceImpl debitAmountService; + + public DebitProcessorImpl(TransactionStatusServicePort transactionStatusService, TransactionServicePort transactionService, BankAccountServicePort bankAccountService, DebitAmountServiceImpl debitAmountService) { + super(transactionStatusService, transactionService, bankAccountService); + this.debitAmountService = debitAmountService; + } + + @Override + protected Transaction processType(DigitalTransactionProcessingDetails digitalTransactionProcessingDetails) { + digitalTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_BEFORE_KEY, digitalTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + digitalTransactionProcessingDetails.getMetadata().put(RECEIVER_AMOUNT_BEFORE_KEY, digitalTransactionProcessingDetails.getReceiverBankAccount().getBalance().getAmount().toString()); + debitAmountService.transferBetweenAccounts(digitalTransactionProcessingDetails); + digitalTransactionProcessingDetails.getMetadata().put(EMITTER_AMOUNT_AFTER_KEY, digitalTransactionProcessingDetails.getEmitterBankAccount().getBalance().getAmount().toString()); + digitalTransactionProcessingDetails.getMetadata().put(RECEIVER_AMOUNT_AFTER_KEY, digitalTransactionProcessingDetails.getReceiverBankAccount().getBalance().getAmount().toString()); + return digitalTransactionProcessingDetails.getTransaction(); + } + + @Override + protected String formatError(Transaction transaction, String errorStatus, Exception exception) { + return MessageFormatter.format(DEBIT_TRANSACTION_CONTEXT, DEBIT_ACTION, errorStatus, + Optional.of(DOMAIN_ERROR), + List.of(TRANSACTION_ID_DETAIL + transaction.getId(), + ERROR_DETAIL + exception.getMessage())); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/status/TransactionStatusServiceImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/status/TransactionStatusServiceImpl.java index 4263792c..61250e34 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/status/TransactionStatusServiceImpl.java +++ b/application/src/main/java/com/cdx/bas/application/bank/transaction/status/TransactionStatusServiceImpl.java @@ -6,22 +6,34 @@ import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; import com.cdx.bas.domain.message.MessageFormatter; -import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.OUTSTANDING; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.metadata.MetadataFieldNames.ERROR_KEY; -@RequestScoped +@ApplicationScoped public class TransactionStatusServiceImpl implements TransactionStatusServicePort { TransactionRepository transactionPersistencePort; + public TransactionStatus handleError(Exception exception, Map metadata) { + if (exception instanceof NoSuchElementException) { + metadata.put(ERROR_KEY, exception.getMessage()); + return TransactionStatus.ERROR; + } else { + metadata.put(ERROR_KEY, exception.getMessage()); + return TransactionStatus.REFUSED; + } + } + @Inject public TransactionStatusServiceImpl(TransactionRepository transactionPersistencePort) { this.transactionPersistencePort = transactionPersistencePort; @@ -46,7 +58,7 @@ public Transaction setStatus(Transaction transaction, TransactionStatus status, throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, CHANGE_STATUS_ACTION, IS_NULL_STATUS)); } transaction.setStatus(status); - transaction.setMetadata(metadata); + transaction.getMetadata().putAll(metadata); return transaction; } diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorImpl.java deleted file mode 100644 index 3fca9ee8..00000000 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; - -public class CreditProcessorImpl implements DigitalTransactionProcessor { - @Override - public void processDigital(Transaction transaction, BankAccount emitterBankAccount, BankAccount receiverBankAccount) throws TransactionException, BankAccountException { - - } -} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorImpl.java deleted file mode 100644 index 729b6464..00000000 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; - -public class DebitProcessorImpl implements DigitalTransactionProcessor{ - @Override - public void processDigital(Transaction transaction, BankAccount emitterBankAccount, BankAccount receiverBankAccount) throws TransactionException, BankAccountException { - - } -} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorImpl.java deleted file mode 100644 index 2c1f8bb9..00000000 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; - -public class DepositProcessorImpl implements PhysicalCashTransactionProcessor{ - @Override - public void processPhysicalCash(Transaction transaction, BankAccount emitterBankAccount) throws TransactionException, BankAccountException { - - } -} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DigitalTransactionProcessor.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DigitalTransactionProcessor.java deleted file mode 100644 index 275ca999..00000000 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/DigitalTransactionProcessor.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; - -@FunctionalInterface -public interface DigitalTransactionProcessor { - void processDigital(Transaction transaction, BankAccount emitterBankAccount, BankAccount receiverBankAccount) throws TransactionException, BankAccountException; -} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/PhysicalCashTransactionProcessor.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/type/PhysicalCashTransactionProcessor.java deleted file mode 100644 index f9080197..00000000 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/PhysicalCashTransactionProcessor.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; - -@FunctionalInterface -public interface PhysicalCashTransactionProcessor { - void processPhysicalCash(Transaction transaction, BankAccount emitterBankAccount) throws TransactionException, BankAccountException; -} diff --git a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImpl.java b/application/src/main/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImpl.java deleted file mode 100644 index f7d33e25..00000000 --- a/application/src/main/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImpl.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.account.BankAccountServicePort; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; -import com.cdx.bas.domain.bank.transaction.TransactionServicePort; -import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; -import com.cdx.bas.domain.bank.transaction.type.TransactionProcessorServicePort; -import com.cdx.bas.domain.bank.transaction.validation.validator.TransactionValidator; -import com.cdx.bas.domain.message.MessageFormatter; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.transaction.Transactional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.*; -import static com.cdx.bas.domain.message.CommonMessages.*; -import static com.cdx.bas.domain.metadata.MetadataFieldNames.*; - - -@ApplicationScoped -public class TransactionProcessorServiceImpl implements TransactionProcessorServicePort { - - private static final Logger logger = LoggerFactory.getLogger(TransactionProcessorServiceImpl.class); - - private final TransactionValidator transactionValidator; - private final TransactionStatusServicePort transactionStatusService; - private final TransactionServicePort transactionService; - private final BankAccountServicePort bankAccountService; - - @Inject - public TransactionProcessorServiceImpl(TransactionValidator transactionValidator, - TransactionStatusServicePort transactionStatusService, - TransactionServicePort transactionService, - BankAccountServicePort bankAccountService) { - this.transactionValidator = transactionValidator; - this.transactionStatusService = transactionStatusService; - this.transactionService = transactionService; - this.bankAccountService = bankAccountService; - } - - @Override - @Transactional - public Transaction credit(Transaction transaction) { - Map metadata = new HashMap<>(); - TransactionStatus transactionStatus = COMPLETED; - try { - BankAccount emitterBankAccount = bankAccountService.findBankAccount(transaction.getEmitterAccountId()); - BankAccount receiverBankAccount = bankAccountService.findBankAccount(transaction.getReceiverAccountId()); - Transaction currentTransaction = transactionStatusService.setAsOutstanding(transaction); - - metadata = new HashMap<>(); - metadata.put(EMITTER_AMOUNT_BEFORE_KEY, emitterBankAccount.getBalance().getAmount().toString()); - metadata.put(RECEIVER_AMOUNT_BEFORE_KEY, receiverBankAccount.getBalance().getAmount().toString()); - bankAccountService.creditAmountToAccounts(currentTransaction, emitterBankAccount, receiverBankAccount); - metadata.put(EMITTER_AMOUNT_AFTER_KEY, emitterBankAccount.getBalance().getAmount().toString()); - metadata.put(RECEIVER_AMOUNT_AFTER_KEY, receiverBankAccount.getBalance().getAmount().toString()); - - Transaction completedTransaction = transactionStatusService.setStatus(currentTransaction, COMPLETED, metadata); - bankAccountService.updateBankAccount(emitterBankAccount); - bankAccountService.updateBankAccount(receiverBankAccount); - - logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, CREDIT_ACTION, COMPLETED_STATUS, - List.of(CREDIT_DETAIL + currentTransaction.getAmount(), - EMITTER_BANK_ACCOUNT_DETAIL + currentTransaction.getEmitterAccountId(), - RECEIVER_BANK_ACCOUNT_DETAIL + currentTransaction.getReceiverAccountId()))); - return completedTransaction; - } catch (NoSuchElementException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = ERROR; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, CREDIT_ACTION, FAILED_STATUS, - Optional.of(NOT_FOUND_CAUSE), - List.of(TRANSACTION_ID_DETAIL + transaction.getId(), ERROR_DETAIL + exception.getMessage()))); - } catch (TransactionException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = REFUSED; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, CREDIT_ACTION, REFUSED_STATUS, - Optional.of(TRANSACTION_ERROR_CAUSE), - List.of(TRANSACTION_ID_DETAIL + transaction.getId(), - ERROR_DETAIL + exception.getMessage()))); - } catch (BankAccountException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = REFUSED; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, CREDIT_ACTION, REFUSED_STATUS, - Optional.of(BANK_ACCOUNT_ERROR_CAUSE), - List.of(TRANSACTION_ID_DETAIL + transaction.getId(), - ERROR_DETAIL + exception.getMessage()))); - } finally { - transaction.setStatus(transactionStatus); - transactionService.update(transaction, metadata); - } - } - - @Override - public Transaction debit(Transaction currentTransaction) { - //TODO feature#45 - currentTransaction.setStatus(REFUSED); - transactionService.update(currentTransaction, new HashMap<>()); - return currentTransaction; - } - - @Override - @Transactional - public Transaction deposit(Transaction currentTransaction) { - Map metadata = new HashMap<>(); - TransactionStatus transactionStatus = COMPLETED; - try { - transactionValidator.validateCashTransaction(currentTransaction); - BankAccount emitterBankAccount = bankAccountService.findBankAccount(currentTransaction.getEmitterAccountId()); - - metadata = new HashMap<>(); - metadata.put(EMITTER_AMOUNT_BEFORE_KEY, emitterBankAccount.getBalance().getAmount().toString()); - bankAccountService.depositAmountToAccount(currentTransaction, emitterBankAccount); - metadata.put(EMITTER_AMOUNT_AFTER_KEY, emitterBankAccount.getBalance().getAmount().toString()); - Transaction completedTransaction = transactionStatusService.setStatus(currentTransaction, COMPLETED, metadata); - bankAccountService.updateBankAccount(emitterBankAccount); - - logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, DEPOSIT_ACTION, COMPLETED_STATUS, - List.of(TRANSACTION_ID_DETAIL + currentTransaction.getId(), - CREDIT_DETAIL + currentTransaction.getAmount(), - EMITTER_BANK_ACCOUNT_DETAIL + currentTransaction.getEmitterAccountId(), - RECEIVER_BANK_ACCOUNT_DETAIL + currentTransaction.getReceiverAccountId()))); - return completedTransaction; - } catch (TransactionException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = REFUSED; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, DEPOSIT_ACTION, REFUSED_STATUS, - Optional.of(TRANSACTION_ERROR_CAUSE), - List.of(TRANSACTION_ID_DETAIL + currentTransaction.getId(), - ERROR_DETAIL + exception.getMessage()))); - } catch (BankAccountException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = REFUSED; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, DEPOSIT_ACTION, REFUSED_STATUS, - Optional.of(BANK_ACCOUNT_ERROR_CAUSE), - List.of(TRANSACTION_ID_DETAIL + currentTransaction.getId(), - ERROR_DETAIL + exception.getMessage()))); - } finally { - currentTransaction.setStatus(transactionStatus); - transactionService.create(currentTransaction, metadata); - } - } - - @Override - @Transactional - public Transaction withdraw(Transaction currentTransaction) { - Map metadata = new HashMap<>(); - TransactionStatus transactionStatus = COMPLETED; - try { - transactionValidator.validateCashTransaction(currentTransaction); - BankAccount emitterBankAccount = bankAccountService.findBankAccount(currentTransaction.getEmitterAccountId()); - metadata = new HashMap<>(); - metadata.put(EMITTER_AMOUNT_BEFORE_KEY, emitterBankAccount.getBalance().getAmount().toString()); - bankAccountService.withdrawAmountToAccount(currentTransaction, emitterBankAccount); - metadata.put(EMITTER_AMOUNT_AFTER_KEY, emitterBankAccount.getBalance().getAmount().toString()); - Transaction completedTransaction = transactionStatusService.setStatus(currentTransaction, COMPLETED, metadata); - bankAccountService.updateBankAccount(emitterBankAccount); - - logger.debug(MessageFormatter.format(TRANSACTION_CONTEXT, WITHDRAW_ACTION, COMPLETED_STATUS, - List.of(TRANSACTION_ID_DETAIL + currentTransaction.getId(), - CREDIT_DETAIL + currentTransaction.getAmount(), - EMITTER_BANK_ACCOUNT_DETAIL + currentTransaction.getEmitterAccountId(), - RECEIVER_BANK_ACCOUNT_DETAIL + currentTransaction.getReceiverAccountId()))); - return completedTransaction; - } catch (TransactionException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = REFUSED; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, WITHDRAW_ACTION, REFUSED_STATUS, - Optional.of(TRANSACTION_ERROR_CAUSE), - List.of(TRANSACTION_ID_DETAIL + currentTransaction.getId(), - ERROR_DETAIL + exception.getMessage()))); - } catch (BankAccountException exception) { - metadata = Map.of(ERROR_KEY, exception.getMessage()); - transactionStatus = REFUSED; - throw new TransactionException(MessageFormatter.format(TRANSACTION_CONTEXT, WITHDRAW_ACTION, REFUSED_STATUS, - Optional.of(BANK_ACCOUNT_ERROR_CAUSE), - List.of(TRANSACTION_ID_DETAIL + currentTransaction.getId(), - ERROR_DETAIL + exception.getMessage()))); - } finally { - currentTransaction.setStatus(transactionStatus); - transactionService.create(currentTransaction, metadata); - } - } -} - - diff --git a/application/src/main/java/com/cdx/bas/application/config/Producers.java b/application/src/main/java/com/cdx/bas/application/config/Producers.java index 2630e98e..f72b4091 100644 --- a/application/src/main/java/com/cdx/bas/application/config/Producers.java +++ b/application/src/main/java/com/cdx/bas/application/config/Producers.java @@ -1,11 +1,13 @@ package com.cdx.bas.application.config; +import com.cdx.bas.domain.testing.Generated; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; import jakarta.ws.rs.Produces; import java.time.Clock; +@Generated @ApplicationScoped public class Producers { diff --git a/application/src/main/java/com/cdx/bas/application/mapper/MappingType.java b/application/src/main/java/com/cdx/bas/application/mapper/MappingType.java deleted file mode 100644 index e85485c5..00000000 --- a/application/src/main/java/com/cdx/bas/application/mapper/MappingType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.cdx.bas.application.mapper; - -public enum MappingType { - CREATE, UPDATE, DELETE -} diff --git a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountMapperTest.java b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountMapperTest.java index 598ae525..564f73e2 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountMapperTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountMapperTest.java @@ -1,25 +1,26 @@ package com.cdx.bas.application.bank.account; import com.cdx.bas.application.bank.customer.CustomerEntity; +import com.cdx.bas.application.bank.customer.CustomerMapper; import com.cdx.bas.application.bank.customer.CustomerRepository; import com.cdx.bas.application.bank.transaction.TransactionEntity; -import com.cdx.bas.application.mapper.DtoEntityMapper; +import com.cdx.bas.application.bank.transaction.TransactionMapper; import com.cdx.bas.domain.bank.account.BankAccount; import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; import com.cdx.bas.domain.bank.account.type.AccountType; import com.cdx.bas.domain.bank.customer.Customer; import com.cdx.bas.domain.bank.customer.gender.Gender; import com.cdx.bas.domain.bank.customer.maritalstatus.MaritalStatus; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; import com.cdx.bas.domain.money.Money; -import io.quarkus.test.InjectMock; -import io.quarkus.test.common.WithTestResource; -import io.quarkus.test.h2.H2DatabaseTestResource; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; import java.time.Instant; @@ -27,31 +28,30 @@ import java.time.Month; import java.util.*; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.CREDIT; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.ERROR; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.CREDIT; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.*; -@QuarkusTest -@WithTestResource(H2DatabaseTestResource.class) -public class BankAccountMapperTest { +@ExtendWith(MockitoExtension.class) +class BankAccountMapperTest { - @Inject - BankAccountMapper bankAccountMapper; - - @InjectMock - DtoEntityMapper customerMapper; + @Mock + CustomerMapper customerMapper; - @InjectMock - DtoEntityMapper transactionMapper; + @Mock + TransactionMapper transactionMapper; - @InjectMock + @Mock CustomerRepository customerRepository; - + + @InjectMocks + BankAccountMapper bankAccountMapper; + @Test - public void toDto_shouldReturnNullDto_whenEntityIsNull() { + void toDto_shouldReturnNullDto_whenEntityIsNull() { BankAccount dto = bankAccountMapper.toDto(null); assertThat(dto).isNull(); @@ -60,7 +60,7 @@ public void toDto_shouldReturnNullDto_whenEntityIsNull() { } @Test - public void toEntity_shouldReturnNullEntity_whenDtoIsNull() { + void toEntity_shouldReturnNullEntity_whenDtoIsNull() { // Act BankAccountEntity entity = bankAccountMapper.toEntity(null); @@ -70,7 +70,7 @@ public void toEntity_shouldReturnNullEntity_whenDtoIsNull() { } @Test - public void toDto_shouldReturnNullDto_whenEntityHasEmptyObject() { + void toDto_shouldReturnNullDto_whenEntityHasEmptyObject() { // Act BankAccount dto = bankAccountMapper.toDto(new BankAccountEntity()); @@ -80,7 +80,7 @@ public void toDto_shouldReturnNullDto_whenEntityHasEmptyObject() { } @Test - public void toDto_shouldMapAccountTypeOnly_whenEntityHasAccount_withOnlyAccountType() { + void toDto_shouldMapAccountTypeOnly_whenEntityHasAccount_withOnlyAccountType() { // Arrange BankAccountEntity entity = new BankAccountEntity(); entity.setType(AccountType.CHECKING); @@ -98,7 +98,7 @@ public void toDto_shouldMapAccountTypeOnly_whenEntityHasAccount_withOnlyAccountT } @Test - public void toEntity_shouldMapNullValues_whenDtoHasAccount_withOnlyAccountTypeAndId() { + void toEntity_shouldMapNullValues_whenDtoHasAccount_withOnlyAccountTypeAndId() { // Arrange BankAccount bankAccount = new CheckingBankAccount(); bankAccount.setId(1L); @@ -116,69 +116,91 @@ public void toEntity_shouldMapNullValues_whenDtoHasAccount_withOnlyAccountTypeAn } @Test - public void toDto_shouldMapEveryFieldsOfDto_whenEntityHasValues() { + void toDto_shouldMapEveryFieldsOfDto_whenEntityHasValues() { // Arrange Instant timestamp = Instant.now(); BankAccountEntity entity = new BankAccountEntity(); entity.setId(10L); - entity.setType(AccountType.CHECKING); + entity.setType(AccountType.SAVING); entity.setBalance(new BigDecimal("1000")); + Set customers = new HashSet<>(); CustomerEntity customerEntity = createCustomerEntityUtils(); customers.add(customerEntity); entity.setCustomers(customers); - Set transactionEntities = new HashSet<>(); - TransactionEntity transactionEntity1 = createTransactionEntity(2000L, timestamp); - transactionEntities.add(transactionEntity1); - TransactionEntity transactionEntity2 = createTransactionEntity(5000L, timestamp); - transactionEntities.add(transactionEntity2); - entity.setIssuedTransactions(transactionEntities); - - Transaction transaction1 = Transaction.builder() - .id(2L) - .type(CREDIT) - .emitterAccountId(2000L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build(); - Transaction transaction2 = Transaction.builder() - .id(2L) - .type(CREDIT) - .emitterAccountId(5000L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build(); - when(transactionMapper.toDto(transactionEntity1)).thenReturn(transaction1); - when(transactionMapper.toDto(transactionEntity2)).thenReturn(transaction2); + + Set issuedTransactionEntities = new HashSet<>(); + TransactionEntity issuedTransactionEntity1 = getTransactionEntity(1L, timestamp); + TransactionEntity issuedTransactionEntity2 = getTransactionEntity(2L, timestamp); + issuedTransactionEntities.add(issuedTransactionEntity1); + issuedTransactionEntities.add(issuedTransactionEntity2); + entity.setIssuedTransactions(issuedTransactionEntities); + + Set incomingTransactionEntities = new HashSet<>(); + TransactionEntity incomingTransactionEntity = getTransactionEntity(3L, timestamp); + incomingTransactionEntities.add(incomingTransactionEntity); + entity.setIncomingTransactions(incomingTransactionEntities); + + Transaction issuedTransaction1 = getTransaction(1L, 10L, 15L, timestamp); + Transaction issuedTransaction2 = getTransaction(2L, 10L, 20L, timestamp); + Transaction incomingTransaction = getTransaction(3L, 30L, 10L, timestamp); + + when(transactionMapper.toDto(issuedTransactionEntity1)).thenReturn(issuedTransaction1); + when(transactionMapper.toDto(issuedTransactionEntity2)).thenReturn(issuedTransaction2); + when(transactionMapper.toDto(incomingTransactionEntity)).thenReturn(incomingTransaction); + + BankAccount expectedBankAccount = new SavingBankAccount(); + expectedBankAccount.setId(10L); + expectedBankAccount.setType(AccountType.SAVING); + expectedBankAccount.setBalance(new Money(new BigDecimal("1000"))); + expectedBankAccount.setCustomersId(Set.of(99L)); + expectedBankAccount.setIssuedTransactions(Set.of(issuedTransaction1, issuedTransaction2)); + expectedBankAccount.setIncomingTransactions(Set.of(incomingTransaction)); // Act BankAccount dto = bankAccountMapper.toDto(entity); // Assert - assertThat(dto.getId()).isEqualTo(10L); - assertThat(dto.getType()).isEqualTo(AccountType.CHECKING); - assertThat(dto.getBalance()).usingRecursiveComparison().isEqualTo(new Money(new BigDecimal("1000"))); - assertThat(dto.getCustomersId()).hasSize(1); - assertThat(dto.getCustomersId().iterator().next()).isEqualTo(99L); - assertThat(dto.getIssuedTransactions()).hasSize(2); - assertThat(dto.getIssuedTransactions()).contains(transaction1); - assertThat(dto.getIssuedTransactions()).contains(transaction2); - - verify(transactionMapper).toDto(transactionEntity1); - verify(transactionMapper).toDto(transactionEntity2); + assertThat(dto) + .usingRecursiveComparison() + .isEqualTo(expectedBankAccount); + + verify(transactionMapper).toDto(issuedTransactionEntity1); + verify(transactionMapper).toDto(issuedTransactionEntity2); + verify(transactionMapper).toDto(incomingTransactionEntity); verifyNoMoreInteractions(customerMapper, transactionMapper); } + private static TransactionEntity getTransactionEntity(long id, Instant timestamp) { + TransactionEntity issuedTransactionEntity2 = new TransactionEntity(); + issuedTransactionEntity2.setId(id); + issuedTransactionEntity2.setEmitterBankAccountEntity(null); + issuedTransactionEntity2.setReceiverBankAccountEntity(null); + issuedTransactionEntity2.setAmount(new BigDecimal("100")); + issuedTransactionEntity2.setType(TransactionType.CREDIT); + issuedTransactionEntity2.setStatus(TransactionStatus.ERROR); + issuedTransactionEntity2.setDate(timestamp); + issuedTransactionEntity2.setLabel("transaction test"); + return issuedTransactionEntity2; + } + + private static Transaction getTransaction(long id, long emitterAccountId, long receiverAccountId, Instant timestamp) { + Transaction incomingTransaction = new Transaction(); + incomingTransaction.setId(id); + incomingTransaction.setType(CREDIT); + incomingTransaction.setEmitterAccountId(emitterAccountId); + incomingTransaction.setReceiverAccountId(receiverAccountId); + incomingTransaction.setAmount(new BigDecimal("100")); + incomingTransaction.setCurrency("EUR"); + incomingTransaction.setStatus(ERROR); + incomingTransaction.setDate(timestamp); + incomingTransaction.setLabel("transaction test"); + return incomingTransaction; + } + + @Test - public void toEntity_shouldThrowNoSuchElementException_whenCustomerIsNotFound() { + void toEntity_shouldThrowNoSuchElementException_whenCustomerIsNotFound() { // Arrange Instant timestamp = Instant.now(); BankAccount dto = new CheckingBankAccount(); @@ -190,34 +212,31 @@ public void toEntity_shouldThrowNoSuchElementException_whenCustomerIsNotFound() customers.add(customer.getId()); dto.setCustomersId(customers); Set transactions = new HashSet<>(); - Transaction transaction1 = Transaction.builder() - .id(2L) - .emitterAccountId(2000L) - .receiverAccountId(77L) - .amount(new BigDecimal(100)) - .type(TransactionType.CREDIT) - .status(TransactionStatus.ERROR) - .date(timestamp) - .label("transaction test") - .metadata(Map.of("amount_before", "0", "amount_after", "350")) - .build(); + Transaction transaction1 = new Transaction(); + transaction1.setId(2L); + transaction1.setEmitterAccountId(2000L); + transaction1.setReceiverAccountId(77L); + transaction1.setAmount(new BigDecimal(100)); + transaction1.setType(TransactionType.CREDIT); + transaction1.setStatus(TransactionStatus.ERROR); + transaction1.setDate(timestamp); + transaction1.setLabel("transaction test"); + transaction1.setMetadata(Map.of("amount_before", "0", "amount_after", "350")); transactions.add(transaction1); - Transaction transaction2 = Transaction.builder() - .id(2L) - .emitterAccountId(5000L) - .receiverAccountId(77L) - .amount(new BigDecimal(100)) - .type(TransactionType.CREDIT) - .status(TransactionStatus.ERROR) - .date(timestamp) - .label("transaction test") - .metadata(Map.of("amount_before", "0", "amount_after", "350")) - .build(); + + Transaction transaction2 = new Transaction(); + transaction2.setId(2L); + transaction2.setEmitterAccountId(5000L); + transaction2.setReceiverAccountId(77L); + transaction2.setAmount(new BigDecimal(100)); + transaction2.setType(TransactionType.CREDIT); + transaction2.setStatus(TransactionStatus.ERROR); + transaction2.setDate(timestamp); + transaction2.setLabel("transaction test"); + transaction2.setMetadata(Map.of("amount_before", "0", "amount_after", "350")); transactions.add(transaction2); dto.setIssuedTransactions(transactions); - when(customerRepository.findById(anyLong())).thenReturn(Optional.empty()); - try { // Act bankAccountMapper.toEntity(dto); @@ -233,7 +252,7 @@ public void toEntity_shouldThrowNoSuchElementException_whenCustomerIsNotFound() } @Test - public void toEntity_shouldMapEveryFieldsOfEntity_whenDtoHasValues() { + void toEntity_shouldMapEveryFieldsOfEntity_whenDtoHasValues() { // Arrange Instant timestamp = Instant.now(); BankAccount dto = new CheckingBankAccount(); @@ -245,38 +264,36 @@ public void toEntity_shouldMapEveryFieldsOfEntity_whenDtoHasValues() { customers.add(customer.getId()); dto.setCustomersId(customers); Set transactions = new HashSet<>(); - Transaction transaction1 = Transaction.builder() - .id(2L) - .emitterAccountId(2000L) - .receiverAccountId(77L) - .amount(new BigDecimal(100)) - .type(TransactionType.CREDIT) - .status(TransactionStatus.ERROR) - .date(timestamp) - .label("transaction test") - .metadata(Map.of("amount_before", "0", "amount_after", "350")) - .build(); + Transaction transaction1 = new Transaction(); + transaction1.setId(1L); + transaction1.setEmitterAccountId(2000L); + transaction1.setReceiverAccountId(77L); + transaction1.setAmount(new BigDecimal(100)); + transaction1.setType(TransactionType.CREDIT); + transaction1.setStatus(TransactionStatus.ERROR); + transaction1.setDate(timestamp); + transaction1.setLabel("transaction test"); + transaction1.setMetadata(Map.of("amount_before", "0", "amount_after", "350")); transactions.add(transaction1); - Transaction transaction2 = Transaction.builder() - .id(2L) - .emitterAccountId(5000L) - .receiverAccountId(77L) - .amount(new BigDecimal(100)) - .type(TransactionType.CREDIT) - .status(TransactionStatus.ERROR) - .date(timestamp) - .label("transaction test") - .metadata(Map.of("amount_before", "0", "amount_after", "350")) - .build(); + + Transaction transaction2 = new Transaction(); + transaction2.setId(2L); + transaction2.setEmitterAccountId(5000L); + transaction2.setReceiverAccountId(77L); + transaction2.setAmount(new BigDecimal(100)); + transaction2.setType(TransactionType.CREDIT); + transaction2.setStatus(TransactionStatus.ERROR); + transaction2.setDate(timestamp); + transaction2.setLabel("transaction test"); + transaction2.setMetadata(Map.of("amount_before", "0", "amount_after", "350")); transactions.add(transaction2); dto.setIssuedTransactions(transactions); CustomerEntity customerEntity = createCustomerEntityUtils(); when(customerRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(customerEntity)); - when(customerMapper.toEntity(customer)).thenReturn(customerEntity); - TransactionEntity transactionEntity1 = createTransactionEntity(2000L, timestamp); + TransactionEntity transactionEntity1 = getTransactionEntity(1L, timestamp); when(transactionMapper.toEntity(transaction1)).thenReturn(transactionEntity1); - TransactionEntity transactionEntity2 = createTransactionEntity(5000L, timestamp); + TransactionEntity transactionEntity2 = getTransactionEntity(2L, timestamp); when(transactionMapper.toEntity(transaction2)).thenReturn(transactionEntity2); // Act @@ -330,16 +347,4 @@ private CustomerEntity createCustomerEntityUtils() { return customerEntity; } - private TransactionEntity createTransactionEntity(long id, Instant instantDate) { - TransactionEntity transactionEntity = new TransactionEntity(); - transactionEntity.setId(id); - transactionEntity.setEmitterBankAccountEntity(null); - transactionEntity.setReceiverBankAccountEntity(null); - transactionEntity.setAmount(new BigDecimal("100")); - transactionEntity.setType(TransactionType.CREDIT); - transactionEntity.setStatus(TransactionStatus.ERROR); - transactionEntity.setDate(instantDate); - transactionEntity.setLabel("transaction test"); - return transactionEntity; - } } diff --git a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountRepositoryTest.java b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountRepositoryTest.java index 7530a7aa..0eb598dd 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountRepositoryTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountRepositoryTest.java @@ -1,13 +1,17 @@ package com.cdx.bas.application.bank.account; +import com.cdx.bas.application.bank.customer.CustomerRepository; +import com.cdx.bas.application.bank.transaction.TransactionRepository; import com.cdx.bas.domain.bank.account.BankAccount; import com.cdx.bas.domain.bank.account.BankAccountPersistencePort; import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; import com.cdx.bas.domain.bank.account.mma.MMABankAccount; import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; import com.cdx.bas.domain.bank.account.type.AccountType; +import com.cdx.bas.domain.bank.customer.Customer; import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionPersistencePort; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import com.cdx.bas.domain.money.Money; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; @@ -26,8 +30,6 @@ import java.util.Optional; import java.util.Set; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.DEPOSIT; import static org.assertj.core.api.Assertions.assertThat; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @@ -36,10 +38,13 @@ class BankAccountRepositoryTest { @Inject - BankAccountPersistencePort bankAccountRepository; + BankAccountRepository bankAccountRepository; @Inject - TransactionPersistencePort transactionRepository; + CustomerRepository customerRepository; + + @Inject + TransactionRepository transactionRepository; @Test @Transactional @@ -64,11 +69,10 @@ void shouldReturnAllBankAccounts_whenAccountsFound() { assertThat(allBankAccounts) .hasSize(expectedAccounts.size()) .usingRecursiveComparison() - .ignoringFields("customersId", "issuedTransactions") + .ignoringFields("customersId", "issuedTransactions", "incomingTransactions") .isEqualTo(expectedAccounts); } - @Test @Transactional void shouldReturnBankAccount_whenAccountIsFound() { @@ -83,7 +87,7 @@ void shouldReturnBankAccount_whenAccountIsFound() { assertThat(optionalBankAccount).isPresent(); assertThat(optionalBankAccount.get()) .usingRecursiveComparison() - .ignoringFields("customersId", "issuedTransactions") + .ignoringFields("customersId", "issuedTransactions", "incomingTransactions") .isEqualTo(bankAccount); } @@ -103,7 +107,7 @@ void shouldReturnEmptyOptional_whenAccountIsNotFound() { @Order(2) void shouldCreateBankAccountSuccessfully() { // Arrange - long id = 10L; + long id = 20L; BankAccount bankAccount = new CheckingBankAccount(); bankAccount.setType(AccountType.CHECKING); bankAccount.setBalance(new Money(new BigDecimal("0"))); @@ -126,32 +130,31 @@ void shouldCreateBankAccountSuccessfully() { @Order(3) void shouldUpdateBankAccountSuccessfully() { // Arrange - long id = 7L; + long id = 8L; Instant timestamp = Instant.now(); BankAccount bankAccount = new CheckingBankAccount(); bankAccount.setId(id); - bankAccount.setType(AccountType.CHECKING); - bankAccount.setBalance(new Money(new BigDecimal("0.00"))); + bankAccount.setType(AccountType.SAVING); + bankAccount.setBalance(new Money(new BigDecimal("200000.00"))); bankAccount.setCustomersId(Set.of(6L)); - Transaction transaction = Transaction.builder() - .emitterAccountId(id) - .amount(new BigDecimal("1000.00")) - .currency("EUR") - .type(DEPOSIT) - .status(UNPROCESSED) - .date(timestamp) - .label("first deposit") - .metadata(Map.of("bill", "500,500")) - .build(); + Transaction transaction = new Transaction(); + transaction.setEmitterAccountId(id); + transaction.setAmount(new BigDecimal("5000.00")); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEPOSIT); + transaction.setStatus(TransactionStatus.UNPROCESSED); + transaction.setDate(timestamp); + transaction.setLabel("transaction 8"); + transaction.setMetadata(Map.of("bill", "2500,2500")); bankAccount.getIssuedTransactions().add(transaction); + // Act - BankAccount updateBankAccount = bankAccountRepository.update(bankAccount); + BankAccount updated = bankAccountRepository.update(bankAccount); // Assert - Optional updatedAccount = bankAccountRepository.findById(updateBankAccount.getId()); - bankAccount.setId(id); - transaction.setId(updateBankAccount.getIssuedTransactions().iterator().next().getId()); + Optional updatedAccount = ((BankAccountPersistencePort) bankAccountRepository).findById(updated.getId()); + transaction.setId(updated.getIssuedTransactions().iterator().next().getId()); assertThat(updatedAccount).isPresent(); assertThat(updatedAccount.get()) .usingRecursiveComparison() @@ -163,11 +166,15 @@ void shouldUpdateBankAccountSuccessfully() { @Order(4) void shouldDeleteBankAccountSuccessfully_whenAccountExists() { // Act - long id = 10L; + long id = 3L; + + // Arrange Optional deletedAccount = bankAccountRepository.deleteById(id); // Assert assertThat(deletedAccount).isNotEmpty(); + Optional customer = customerRepository.findById(4L); + assertThat(customer).isPresent(); assertThat(bankAccountRepository.findById(id)).isEmpty(); } 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 index 5df9b037..f2185864 100644 --- 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 @@ -2,123 +2,92 @@ 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.bank.account.checking.CheckingBankAccount; -import com.cdx.bas.domain.bank.account.validation.BankAccountValidator; +import com.cdx.bas.domain.bank.account.mma.MMABankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; +import com.cdx.bas.domain.bank.account.type.AccountType; import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; -import com.cdx.bas.domain.bank.transaction.TransactionServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import com.cdx.bas.domain.money.Money; -import io.quarkus.test.InjectMock; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import java.math.BigDecimal; import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; -import static com.cdx.bas.domain.bank.account.type.AccountType.CHECKING; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.ERROR; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.*; @QuarkusTest @WithTestResource(H2DatabaseTestResource.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class BankAccountServiceTest { - @InjectMock - BankAccountPersistencePort bankAccountRepository; - - @InjectMock - BankAccountValidator bankAccountValidator; - - @InjectMock - TransactionServicePort transactionService; - @Inject BankAccountServicePort bankAccountService; @Test + @Order(1) void shouldGetAllBankAccounts_whenRepositoryFoundBankAccounts() { // Arrange - BankAccount bankAccount1 = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - BankAccount bankAccount2 = CheckingBankAccount.builder() - .id(100L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("10"))) - .customersId(Set.of(100L)) - .build(); - - BankAccount bankAccount3 = CheckingBankAccount.builder() - .id(101L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("20"))) - .customersId(Set.of(101L)) - .build(); - List bankAccounts = List.of(bankAccount1, bankAccount2, bankAccount3); - when(bankAccountRepository.getAll()).thenReturn(bankAccounts); + List bankAccounts = getAllBankAccounts(); // Act List actualBankAccounts = bankAccountService.getAll(); // Assert - assertThat(actualBankAccounts).containsExactlyInAnyOrderElementsOf(bankAccounts); + assertThat(actualBankAccounts).usingRecursiveComparison() + .ignoringFields("customersId", "issuedTransactions", "incomingTransactions") + .isEqualTo(bankAccounts); } @Test + @Order(2) void shouldFindBankAccount_whenBankAccountExists() { // Arrange - BankAccount bankAccount = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - - when(bankAccountRepository.findById(1L)).thenReturn(Optional.of(bankAccount)); + long id = 1L; + BankAccount bankAccount = new CheckingBankAccount(); + bankAccount.setId(id); + bankAccount.setType(AccountType.CHECKING); // Replace CHECKING with your specific enum if different + bankAccount.setBalance(Money.of(new BigDecimal("400.00"))); + bankAccount.setCustomersId(Set.of(1L)); // Act BankAccount actualBankAccount = bankAccountService.findBankAccount(1L); // Assert - assertThat(actualBankAccount).isEqualTo(bankAccount); - verify(bankAccountRepository).findById(1L); - verifyNoMoreInteractions(bankAccountRepository); - verifyNoInteractions(transactionService); + assertThat(actualBankAccount) + .usingRecursiveComparison() + .ignoringFields("customersId", "issuedTransactions", "incomingTransactions") + .isEqualTo(bankAccount); } @Test void shouldReturnNull_whenBankAccountDoesNotExist() { // Arrange - long id = 1L; - when(bankAccountRepository.findById(id)).thenReturn(Optional.empty()); + long id = 99L; // Act try { - bankAccountService.findBankAccount(1L); + bankAccountService.findBankAccount(id); fail("Bank account does not exist"); } catch (BankAccountException exception) { // Assert - String expectedMessage = "Bank account: searching failed - not found\nBank account id:" + id; + String expectedMessage = "Bank account: searching failed - not found\n" + "Bank account id:" + id; assertThat(exception).hasMessage(expectedMessage); - verify(bankAccountRepository).findById(1L); - verifyNoMoreInteractions(bankAccountRepository, bankAccountValidator); - verifyNoInteractions(transactionService); } } @@ -126,284 +95,148 @@ void shouldReturnNull_whenBankAccountDoesNotExist() { void shouldAddTransactionToBankAccount_whenTransactionDoesNotExist() { // Arrange Instant timestamp = Instant.now(); - BankAccount bankAccount = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .issuedTransactions(new HashSet<>()) - .build(); - - // Act - Transaction transaction = Transaction.builder() - .id(10L) - .type(CREDIT) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build(); + BankAccount bankAccount = new CheckingBankAccount(); + bankAccount.setId(99L); + bankAccount.setType(AccountType.CHECKING); + bankAccount.setBalance(Money.of(new BigDecimal("100"))); + bankAccount.setCustomersId(Set.of(99L)); + bankAccount.setIssuedTransactions(new HashSet<>()); + + Transaction transaction = new Transaction(); + transaction.setId(10L); + transaction.setType(TransactionType.CREDIT); + transaction.setEmitterAccountId(99L); + transaction.setReceiverAccountId(77L); + transaction.setAmount(new BigDecimal("100")); + transaction.setCurrency("EUR"); + transaction.setStatus(TransactionStatus.ERROR); + transaction.setDate(timestamp); + transaction.setLabel("transaction test"); // Assert BankAccount actualBankAccount = bankAccountService.putTransaction(transaction, bankAccount); assertThat(actualBankAccount.getIssuedTransactions()).hasSize(1); assertThat(actualBankAccount.getIssuedTransactions()).contains(transaction); - - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); } @Test void shouldUpdateTransactionToBankAccount_whenTransactionExists() { // Arrange Instant timestamp = Instant.now(); - BankAccount bankAccount = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .issuedTransactions(new HashSet<>()) - .build(); - Transaction transaction = Transaction.builder() - .id(10L) - .type(CREDIT) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build(); + BankAccount bankAccount = new CheckingBankAccount(); + bankAccount.setId(99L); + bankAccount.setType(AccountType.CHECKING); + bankAccount.setBalance(Money.of(new BigDecimal("100"))); + bankAccount.setCustomersId(Set.of(99L)); + bankAccount.setIssuedTransactions(new HashSet<>()); + + Transaction transaction = new Transaction(); + transaction.setId(10L); + transaction.setType(TransactionType.CREDIT); + transaction.setEmitterAccountId(99L); + transaction.setReceiverAccountId(77L); + transaction.setAmount(new BigDecimal("100")); + transaction.setCurrency("EUR"); + transaction.setStatus(TransactionStatus.ERROR); // Assuming ERROR is an enum value + transaction.setDate(timestamp); // Assuming timestamp is a defined Instant variable + transaction.setLabel("transaction test"); bankAccount.getIssuedTransactions().add(transaction); - // Act BankAccount actualBankAccount = bankAccountService.putTransaction(transaction, bankAccount); // Assert assertThat(actualBankAccount.getIssuedTransactions()).hasSize(1); assertThat(actualBankAccount.getIssuedTransactions()).contains(transaction); - verifyNoMoreInteractions(transactionService); - verifyNoInteractions(bankAccountValidator, bankAccountRepository); } @Test void shouldUpdateBankAccount_whenHasValidBankAccount() { // Arrange - BankAccount bankAccount = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); + long id = 8L; + BankAccount bankAccount = new CheckingBankAccount(); + bankAccount.setId(id); + bankAccount.setType(AccountType.CHECKING); + bankAccount.setBalance(Money.of(new BigDecimal("0.00"))); + bankAccount.setCustomersId(Set.of(6L)); + + Transaction transaction9 = new Transaction(); + transaction9.setId(9L); + transaction9.setEmitterAccountId(8L); + transaction9.setReceiverAccountId(7L); + transaction9.setType(TransactionType.DEBIT); + transaction9.setAmount(new BigDecimal("5000.00")); + transaction9.setCurrency("EUR"); + transaction9.setStatus(TransactionStatus.UNPROCESSED); + + ZonedDateTime timestamp = ZonedDateTime.of(2024, 12, 6, 19, 0, 10, 0, ZoneOffset.ofHours(1)); + transaction9.setDate(timestamp.toInstant()); + + transaction9.setLabel("transaction 9"); + transaction9.setMetadata(null); + bankAccount.setIncomingTransactions(Set.of(transaction9)); // Act BankAccount actualBankAccount = bankAccountService.updateBankAccount(bankAccount); // Assert - assertThat(actualBankAccount).isNull(); - verify(bankAccountValidator).validateBankAccount(bankAccount); - verify(bankAccountRepository).update(bankAccount); - verifyNoMoreInteractions(bankAccountValidator, bankAccountRepository); - verifyNoInteractions(transactionService); - } - - @Test - void shouldCreditBankAccountWithAmount_whenHasValidCreditTransaction() { - // Arrange - Transaction transaction = Transaction.builder() - .id(10L) - .type(CREDIT) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(Instant.now()) - .label("transaction test") - .build(); - BankAccount bankAccountEmitter = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - BankAccount bankAccountReceiver = CheckingBankAccount.builder() - .id(100L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("0"))) - .customersId(Set.of(99L)) - .build(); - - // Act - bankAccountService.creditAmountToAccounts(transaction, bankAccountEmitter, bankAccountReceiver); - - // Assert - assertThat(bankAccountEmitter.getBalance().getAmount()).isEqualTo(new BigDecimal("0.0")); - assertThat(bankAccountReceiver.getBalance().getAmount()).isEqualTo(new BigDecimal("100.0")); - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); - } - - @Test - void shouldThrowTransactionException_whenCreditTransactionHasNegativeAmount() { - // Arrange - Transaction transaction = Transaction.builder() - .id(10L) - .type(CREDIT) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("-100")) - .currency("EUR") - .status(ERROR) - .date(Instant.now()) - .label("transaction test") - .build(); - BankAccount bankAccountEmitter = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - BankAccount bankAccountReceiver = CheckingBankAccount.builder() - .id(100L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("0"))) - .customersId(Set.of(99L)) - .build(); - - // Act - try { - bankAccountService.creditAmountToAccounts(transaction, bankAccountEmitter, bankAccountReceiver); - } catch (TransactionException exception) { - // Assert - String expectedMessage = "Credit transaction: credit failed - should have positive value\nTransaction id:10\nEuro amount:-100.0"; - assertThat(exception.getMessage()).isEqualTo(expectedMessage); - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); - } - } - - @Test - void shouldDepositAmountToBankAccount_whenHasValidDepositTransaction() { - // Arrange - Transaction transaction = Transaction.builder() - .id(10L) - .type(DEPOSIT) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(UNPROCESSED) - .date(Instant.now()) - .label("transaction test") - .build(); - BankAccount bankAccountEmitter = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("0"))) - .customersId(Set.of(99L)) - .build(); - - // Act - bankAccountService.depositAmountToAccount(transaction, bankAccountEmitter); - - // Assert - assertThat(bankAccountEmitter.getBalance().getAmount()).isEqualTo(new BigDecimal("100.0")); - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); + BankAccount expectedBankAccount = bankAccountService.findBankAccount(id); + assertThat(actualBankAccount) + .usingRecursiveComparison() + .isEqualTo(expectedBankAccount); } - @Test - void shouldThrowTransactionException_whenDepositTransactionHasNegativeAmount() { - // Arrange - Transaction transaction = Transaction.builder() - .id(10L) - .type(DEPOSIT) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("-100")) - .currency("EUR") - .status(UNPROCESSED) - .date(Instant.now()) - .label("transaction test") - .build(); - BankAccount bankAccountEmitter = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - // Act - try { - bankAccountService.depositAmountToAccount(transaction, bankAccountEmitter); - } catch (TransactionException exception) { - // Assert - String expectedMessage = "Debit transaction: debit failed - should have positive value\nTransaction id:10\nEuro amount:-100.0"; - assertThat(exception.getMessage()).isEqualTo(expectedMessage); - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); - } - } - - @Test - void shouldWithdrawAmountToBankAccount_whenHasValidWithdrawTransaction() { - // Arrange - Transaction transaction = Transaction.builder() - .id(10L) - .type(WITHDRAW) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(UNPROCESSED) - .date(Instant.now()) - .label("transaction test") - .build(); - BankAccount bankAccountEmitter = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - - // Act - bankAccountService.withdrawAmountToAccount(transaction, bankAccountEmitter); - - // Assert - assertThat(bankAccountEmitter.getBalance().getAmount()).isEqualTo(new BigDecimal("0.0")); - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); - } - - @Test - void shouldThrowTransactionException_whenWithdrawTransactionHasNegativeAmount() { - // Arrange - Transaction transaction = Transaction.builder() - .id(10L) - .type(WITHDRAW) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("-100")) - .currency("EUR") - .status(UNPROCESSED) - .date(Instant.now()) - .label("transaction test") - .build(); - BankAccount bankAccountEmitter = CheckingBankAccount.builder() - .id(99L) - .type(CHECKING) - .balance(Money.of(new BigDecimal("100"))) - .customersId(Set.of(99L)) - .build(); - - // Act - try { - bankAccountService.withdrawAmountToAccount(transaction, bankAccountEmitter); - } catch (TransactionException exception) { - // Assert - String expectedMessage = "Withdraw transaction: withdraw failed - should have positive value\nTransaction id:10\nEuro amount:-100.0"; - assertThat(exception.getMessage()).isEqualTo(expectedMessage); - verifyNoInteractions(transactionService, bankAccountValidator, bankAccountRepository); - } + private static List getAllBankAccounts() { + BankAccount bankAccount1 = new CheckingBankAccount(); + bankAccount1.setId(1L); + bankAccount1.setType(AccountType.CHECKING); + bankAccount1.setBalance(Money.of(new BigDecimal("400.00"))); + bankAccount1.setCustomersId(Set.of(1L)); // Replace with appropriate customer IDs if available + + BankAccount bankAccount2 = new CheckingBankAccount(); + bankAccount2.setId(2L); + bankAccount2.setType(AccountType.CHECKING); + bankAccount2.setBalance(Money.of(new BigDecimal("1600.00"))); + bankAccount2.setCustomersId(Set.of(2L)); + + BankAccount bankAccount3 = new SavingBankAccount(); + bankAccount3.setId(3L); + bankAccount3.setType(AccountType.SAVING); + bankAccount3.setBalance(Money.of(new BigDecimal("19200.00"))); + bankAccount3.setCustomersId(Set.of(3L)); + + BankAccount bankAccount4 = new CheckingBankAccount(); + bankAccount4.setId(4L); + bankAccount4.setType(AccountType.CHECKING); + bankAccount4.setBalance(Money.of(new BigDecimal("500.00"))); + bankAccount4.setCustomersId(Set.of(4L)); + + BankAccount bankAccount5 = new MMABankAccount(); // Assuming MMA corresponds to Money Market Account + bankAccount5.setId(5L); + bankAccount5.setType(AccountType.MMA); + bankAccount5.setBalance(Money.of(new BigDecimal("65000.00"))); + bankAccount5.setCustomersId(Set.of(5L)); + + BankAccount bankAccount6 = new SavingBankAccount(); + bankAccount6.setId(6L); + bankAccount6.setType(AccountType.SAVING); + bankAccount6.setBalance(Money.of(new BigDecimal("999.00"))); + bankAccount6.setCustomersId(Set.of(6L)); + + BankAccount bankAccount7 = new CheckingBankAccount(); + bankAccount7.setId(7L); + bankAccount7.setType(AccountType.CHECKING); + bankAccount7.setBalance(Money.of(new BigDecimal("0.00"))); + bankAccount7.setCustomersId(Set.of(7L)); + + BankAccount bankAccount8 = new SavingBankAccount(); + bankAccount8.setId(8L); + bankAccount8.setType(AccountType.SAVING); + bankAccount8.setBalance(Money.of(new BigDecimal("200000.00"))); + bankAccount8.setCustomersId(Set.of(8L)); + + List bankAccounts = List.of(bankAccount1, bankAccount2, bankAccount3, bankAccount4, bankAccount5, bankAccount6, bankAccount7, bankAccount8); + return bankAccounts; } } diff --git a/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerMapperTest.java b/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerMapperTest.java index b2f12466..63ed3ca1 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerMapperTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerMapperTest.java @@ -10,8 +10,8 @@ import com.cdx.bas.domain.bank.customer.gender.Gender; import com.cdx.bas.domain.bank.customer.maritalstatus.MaritalStatus; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; import com.cdx.bas.domain.money.Money; import io.quarkus.test.InjectMock; import io.quarkus.test.common.WithTestResource; @@ -28,8 +28,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.ERROR; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.CREDIT; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; @@ -161,8 +159,8 @@ void toDto_shouldMapDtoValues_whenEntityHasValues() { assertThat(dto.getAccounts()).hasSize(2); Set accountsToCompare = Stream.of(account1, account2).collect(Collectors.toSet()); assertThat(dto.getAccounts()).containsExactlyInAnyOrderElementsOf(accountsToCompare); - assertThat(dto.getMetadata().get("contact_preferences")).isEqualTo("email"); - assertThat(dto.getMetadata().get("annual_salary")).isEqualTo("52000"); + assertThat(dto.getMetadata()).containsEntry("contact_preferences", "email"); + assertThat(dto.getMetadata()).containsEntry("annual_salary","52000"); verify(bankAccountMapper).toDto(accountEntity1); verify(bankAccountMapper).toDto(accountEntity2); verifyNoMoreInteractions(bankAccountMapper); @@ -236,39 +234,41 @@ private BankAccount createBankAccount(long accountId, Instant timestamp) { customersId.add(99L); bankAccount.setCustomersId(customersId); Set transactions = new HashSet<>(); - transactions.add(Transaction.builder() - .id(100L) - .type(CREDIT) - .emitterAccountId(accountId) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build()); - transactions.add(Transaction.builder() - .id(100L) - .type(CREDIT) - .emitterAccountId(accountId) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build()); - transactions.add(Transaction.builder() - .id(100L) - .type(CREDIT) - .emitterAccountId(accountId) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .status(ERROR) - .date(timestamp) - .label("transaction test") - .build()); + Transaction transaction1 = new Transaction(); + transaction1.setId(100L); + transaction1.setType(TransactionType.CREDIT); + transaction1.setEmitterAccountId(accountId); + transaction1.setReceiverAccountId(77L); + transaction1.setAmount(new BigDecimal("100")); + transaction1.setCurrency("EUR"); + transaction1.setStatus(TransactionStatus.ERROR); + transaction1.setDate(timestamp); + transaction1.setLabel("transaction test"); + transactions.add(transaction1); + + Transaction transaction2 = new Transaction(); + transaction2.setId(100L); + transaction2.setType(TransactionType.CREDIT); + transaction2.setEmitterAccountId(accountId); + transaction2.setReceiverAccountId(77L); + transaction2.setAmount(new BigDecimal("100")); + transaction2.setCurrency("EUR"); + transaction2.setStatus(TransactionStatus.ERROR); + transaction2.setDate(timestamp); + transaction2.setLabel("transaction test"); + transactions.add(transaction2); + + Transaction transaction3 = new Transaction(); + transaction3.setId(100L); + transaction3.setType(TransactionType.CREDIT); + transaction3.setEmitterAccountId(accountId); + transaction3.setReceiverAccountId(77L); + transaction3.setAmount(new BigDecimal("100")); + transaction3.setCurrency("EUR"); + transaction3.setStatus(TransactionStatus.ERROR); + transaction3.setDate(timestamp); + transaction3.setLabel("transaction test"); + transactions.add(transaction3); bankAccount.setIssuedTransactions(transactions); return bankAccount; } diff --git a/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerRepositoryTest.java b/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerRepositoryTest.java index cec79978..ba0412a7 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerRepositoryTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerRepositoryTest.java @@ -23,12 +23,12 @@ import static com.cdx.bas.domain.bank.customer.maritalstatus.MaritalStatus.*; import static org.assertj.core.api.Assertions.assertThat; -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @QuarkusTest @WithTestResource(H2DatabaseTestResource.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class CustomerRepositoryTest { - public static final long ID_SEQUENCE_START = 10L; + public static final long ID_SEQUENCE_START = 20L; @Inject CustomerPersistencePort customerRepository; @@ -88,8 +88,10 @@ void create_shouldPersistNewCustomer() { // Assert assertThat(createdCustomer).isEqualTo(newCustomer); Optional customer = customerRepository.findById(ID_SEQUENCE_START); - Customer expectedCustomer = new Customer(ID_SEQUENCE_START, "Paul", "Smith", MALE, SINGLE, LocalDate.of(1990, 5, 20), "UK", "10 Downing St", "London", "paul.smith@bas.com", "+44 20 7946 0958", Collections.emptyList(), Map.of("contact_preferences", "email")); assertThat(customer).isPresent(); + Customer expectedCustomer = new Customer(ID_SEQUENCE_START, "Paul", "Smith", MALE, SINGLE, + LocalDate.of(1990, 5, 20), "UK", "10 Downing St", "London", "paul.smith@bas.com", "+44 20 7946 0958", + Collections.emptyList(), Map.of("contact_preferences", "email")); assertThat(customer.get()) .usingRecursiveComparison() .isEqualTo(expectedCustomer); @@ -100,7 +102,7 @@ void create_shouldPersistNewCustomer() { @Order(4) void update_shouldModifyExistingCustomer() { // Arrange - Optional existingCustomer = customerRepository.findById(10L); + Optional existingCustomer = customerRepository.findById(ID_SEQUENCE_START); assertThat(existingCustomer).isPresent(); Customer customerToUpdate = existingCustomer.get(); customerToUpdate.setFirstName("Johnny"); @@ -120,16 +122,15 @@ void update_shouldModifyExistingCustomer() { @Order(5) void deleteById_shouldRemoveCustomer_whenIdExists() { // Arrange - long customerId = 10L; - assertThat(customerRepository.findById(customerId)).isPresent(); + assertThat(customerRepository.findById(ID_SEQUENCE_START)).isPresent(); // Act - Optional deletedCustomer = customerRepository.deleteById(customerId); + Optional deletedCustomer = customerRepository.deleteById(ID_SEQUENCE_START); // Assert assertThat(deletedCustomer).isPresent(); assertThat(deletedCustomer.get().getFirstName()).isEqualTo("Johnny"); - assertThat(customerRepository.findById(customerId)).isNotPresent(); + assertThat(customerRepository.findById(ID_SEQUENCE_START)).isNotPresent(); } @Test diff --git a/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerServiceImplTest.java index 845f5310..eb8a4681 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerServiceImplTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/customer/CustomerServiceImplTest.java @@ -1,7 +1,191 @@ package com.cdx.bas.application.bank.customer; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; +import com.cdx.bas.domain.bank.account.mma.MMABankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; +import com.cdx.bas.domain.bank.account.type.AccountType; +import com.cdx.bas.domain.bank.customer.Customer; +import com.cdx.bas.domain.bank.customer.CustomerException; +import com.cdx.bas.domain.bank.customer.CustomerServicePort; +import com.cdx.bas.domain.bank.customer.gender.Gender; +import com.cdx.bas.domain.bank.customer.maritalstatus.MaritalStatus; +import com.cdx.bas.domain.money.Money; +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.h2.H2DatabaseTestResource; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import jakarta.inject.Inject; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Set; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@QuarkusTest +@WithTestResource(H2DatabaseTestResource.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class CustomerServiceTest { + + @Inject + CustomerServicePort customerService; + + @Test + @Order(1) + void shouldGetAllCustomers_whenRepositoryFoundCustomers() { + // Arrange + Set expectedCustomers = Set.of( + new Customer( + 1L, + "John", + "Doe", + Gender.MALE, + MaritalStatus.SINGLE, + LocalDate.parse("1980-01-01"), + "US", + "200 Central Park West, NY 10024", + "New York", + "johndoe@bas.com", + "+1 212-769-5100", + null, + Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "false") + ), + new Customer( + 2L, + "Anne", + "Jean", + Gender.FEMALE, + MaritalStatus.MARRIED, + LocalDate.parse("1993-07-11"), + "FR", + "2 rue du chateau", + "Marseille", + "annej@bas.com", + "+36 6 50 44 12 05", + null, + Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "false") + ), + new Customer( + 3L, + "Paul", + "Jean", + Gender.MALE, + MaritalStatus.MARRIED, + LocalDate.parse("1992-04-11"), + "FR", + "2 rue du chateau", + "Marseille", + "paulj@bas.com", + "+36 6 50 44 12 05", + null, + Map.of("contact_preferences", "email", "annual_salary", "52000", "newsletter", "false") + ), + new Customer( + 4L, + "Sophie", + "Dupon", + Gender.FEMALE, + MaritalStatus.WIDOWED, + LocalDate.parse("1977-07-14"), + "FR", + "10 rue du louvre", + "Paris", + "Sodup@bas.com", + "+33 6 50 60 12 05", + null, + Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "true") + ), + new Customer( + 5L, + "Andre", + "Martin", + Gender.MALE, + MaritalStatus.DIVORCED, + LocalDate.parse("1989-07-22"), + "FR", + "16 boulevard victor hugo", + "Nîmes", + "andre.martin@bas.com", + "+33 6 50 44 12 05", + null, + Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "true") + ), + new Customer( + 6L, + "Juan", + "Pedros", + Gender.MALE, + MaritalStatus.SINGLE, + LocalDate.parse("1975-12-17"), + "ES", + "Place de las Delicias", + "Sevilla", + "juanito@bas.com", + "+34 9 20 55 62 05", + null, + Map.of("contact_preferences", "phone", "annual_salary", "200000", "newsletter", "false") + ) + ); + + // Act + Set actualCustomers = customerService.getAll(); + + // Assert + assertThat(actualCustomers).usingRecursiveComparison() + .ignoringFields("accounts") + .isEqualTo(expectedCustomers); + } + + @Test + @Order(2) + void shouldFindCustomer_whenCustomerExists() { + // Arrange + long customerId = 1L; + Customer expectedCustomer = new Customer( + 1L, + "John", + "Doe", + Gender.MALE, + MaritalStatus.SINGLE, + LocalDate.parse("1980-01-01"), + "US", + "200 Central Park West, NY 10024", + "New York", + "johndoe@bas.com", + "+1 212-769-5100", + null, + Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "false") + ); + + // Act + Customer actualCustomer = customerService.findCustomer(customerId); + + // Assert + assertThat(actualCustomer) + .usingRecursiveComparison() + .ignoringFields("accounts") + .isEqualTo(expectedCustomer); + } + -class CustomerServiceImplTest { + @Test + @Order(3) + void shouldThrowCustomerException_whenCustomerDoesNotExist() { + // Arrange + long customerId = 99L; -} \ No newline at end of file + // Act & Assert + assertThatThrownBy(() -> customerService.findCustomer(customerId)) + .isInstanceOf(CustomerException.class) + .hasMessageContaining("Customer: searching not found\n" + + "Customer id:" + customerId); + } +} diff --git a/application/src/test/java/com/cdx/bas/application/bank/customer/gender/GenderConverterTest.java b/application/src/test/java/com/cdx/bas/application/bank/customer/gender/GenderConverterTest.java index 7c9a097a..4845ac78 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/customer/gender/GenderConverterTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/customer/gender/GenderConverterTest.java @@ -12,9 +12,9 @@ class GenderConverterTest { @Test void convertToDatabaseColumn_shouldConvertToGenderCode() { - assertThat(genderConverter.convertToDatabaseColumn(MALE)).isEqualTo(strMale); - assertThat(genderConverter.convertToDatabaseColumn(FEMALE)).isEqualTo(strFemale); - assertThat(genderConverter.convertToDatabaseColumn(OTHER)).isEqualTo(strOther); + assertThat(genderConverter.convertToDatabaseColumn(MALE)).isEqualTo(STR_MALE); + assertThat(genderConverter.convertToDatabaseColumn(FEMALE)).isEqualTo(STR_FEMALE); + assertThat(genderConverter.convertToDatabaseColumn(OTHER)).isEqualTo(STR_OTHER); } @Test @@ -24,9 +24,9 @@ void convertToDatabaseColumn_shouldReturnNull_whenGenderIsNull() { @Test void convertToEntityAttribute_shouldConvertToGender() { - assertThat(genderConverter.convertToEntityAttribute(strMale)).isEqualTo(MALE); - assertThat(genderConverter.convertToEntityAttribute(strFemale)).isEqualTo(FEMALE); - assertThat(genderConverter.convertToEntityAttribute(strOther)).isEqualTo(OTHER); + assertThat(genderConverter.convertToEntityAttribute(STR_MALE)).isEqualTo(MALE); + assertThat(genderConverter.convertToEntityAttribute(STR_FEMALE)).isEqualTo(FEMALE); + assertThat(genderConverter.convertToEntityAttribute(STR_OTHER)).isEqualTo(OTHER); } @Test diff --git a/application/src/test/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverterTest.java b/application/src/test/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverterTest.java index 046c6574..451f20e6 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverterTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/customer/maritalstatus/MaritalStatusConverterTest.java @@ -10,35 +10,35 @@ class MaritalStatusConverterTest { private final MaritalStatusConverter maritalStatusConverter = new MaritalStatusConverter(); @Test - public void convertToDatabaseColumn_shouldConvertToMaritalCode() { - assertEquals(strSingle, maritalStatusConverter.convertToDatabaseColumn(SINGLE)); - assertEquals(strMarried, maritalStatusConverter.convertToDatabaseColumn(MARRIED)); - assertEquals(strWidowed, maritalStatusConverter.convertToDatabaseColumn(WIDOWED)); - assertEquals(strDivorced, maritalStatusConverter.convertToDatabaseColumn(DIVORCED)); - assertEquals(strPacs, maritalStatusConverter.convertToDatabaseColumn(PACS)); + void convertToDatabaseColumn_shouldConvertToMaritalCode() { + assertEquals(STR_SINGLE, maritalStatusConverter.convertToDatabaseColumn(SINGLE)); + assertEquals(STR_MARRIED, maritalStatusConverter.convertToDatabaseColumn(MARRIED)); + assertEquals(STR_WIDOWED, maritalStatusConverter.convertToDatabaseColumn(WIDOWED)); + assertEquals(STR_DIVORCED, maritalStatusConverter.convertToDatabaseColumn(DIVORCED)); + assertEquals(STR_PACS, maritalStatusConverter.convertToDatabaseColumn(PACS)); } @Test - public void convertToDatabaseColumn_shouldReturnNull_whenMaritalStatusIsNull() { + void convertToDatabaseColumn_shouldReturnNull_whenMaritalStatusIsNull() { assertNull(maritalStatusConverter.convertToDatabaseColumn(null)); } @Test - public void convertToEntityAttribute_shouldConvertToMaritalStatus() { - assertEquals(SINGLE, maritalStatusConverter.convertToEntityAttribute(strSingle)); - assertEquals(MARRIED, maritalStatusConverter.convertToEntityAttribute(strMarried)); - assertEquals(WIDOWED, maritalStatusConverter.convertToEntityAttribute(strWidowed)); - assertEquals(DIVORCED, maritalStatusConverter.convertToEntityAttribute(strDivorced)); - assertEquals(PACS, maritalStatusConverter.convertToEntityAttribute(strPacs)); + void convertToEntityAttribute_shouldConvertToMaritalStatus() { + assertEquals(SINGLE, maritalStatusConverter.convertToEntityAttribute(STR_SINGLE)); + assertEquals(MARRIED, maritalStatusConverter.convertToEntityAttribute(STR_MARRIED)); + assertEquals(WIDOWED, maritalStatusConverter.convertToEntityAttribute(STR_WIDOWED)); + assertEquals(DIVORCED, maritalStatusConverter.convertToEntityAttribute(STR_DIVORCED)); + assertEquals(PACS, maritalStatusConverter.convertToEntityAttribute(STR_PACS)); } @Test - public void convertToEntityAttribute_shouldReturnNull_whenMaritalCodeIsNull() { + void convertToEntityAttribute_shouldReturnNull_whenMaritalCodeIsNull() { assertNull(maritalStatusConverter.convertToEntityAttribute(null)); } @Test - public void convertToEntityAttribute_shouldThrowExceptionForInvalidDatabaseColumn() { + void convertToEntityAttribute_shouldThrowExceptionForInvalidDatabaseColumn() { assertThrows(IllegalStateException.class, () -> maritalStatusConverter.convertToEntityAttribute('X')); } } diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java index c4cc1076..3c4ee682 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java @@ -5,8 +5,8 @@ import com.cdx.bas.application.bank.customer.CustomerEntity; import com.cdx.bas.domain.bank.account.type.AccountType; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; import io.quarkus.test.InjectMock; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; @@ -16,13 +16,12 @@ import java.math.BigDecimal; import java.time.Instant; +import java.util.HashMap; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.when; @QuarkusTest @@ -36,29 +35,23 @@ class TransactionMapperTest { TransactionMapper transactionMapper; @Test - void toDto_shouldThrowException_whenTransactionDtoDoesNotHaveEmitterBankAccount() { - try { - transactionMapper.toDto(new TransactionEntity()); - fail(); - } catch (NoSuchElementException exception) { - assertThat(exception.getMessage()).hasToString("Transaction does not have emitter bank account."); - } + void toDto_shouldMapEmpty_whenTransactionEntityIsEmpty() { + Transaction actualDto = transactionMapper.toDto(new TransactionEntity()); + + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setMetadata(new HashMap<>()); + assertThat(actualDto).usingRecursiveComparison().isEqualTo(expectedTransaction); } @Test - void toEntity_shouldThrowException_whenTransactionDtoDoesNotHaveEmitterBankAccount() { - try { - Transaction transaction = Transaction.builder() - .id(10L) - .emitterAccountId(null) - .build(); - transactionMapper.toEntity(transaction); - fail(); - } catch (NoSuchElementException exception) { - assertThat(exception.getMessage()).hasToString("Transaction does not have emitter bank account entity."); - } + void toEntity_shouldMapEmpty_whenTransactionDtoIsEmpty() { + TransactionEntity actualEntity = transactionMapper.toEntity(new Transaction()); + + TransactionEntity expectedTransactionEntity = new TransactionEntity(); + expectedTransactionEntity.setMetadata("{}"); + assertThat(actualEntity).isEqualTo(expectedTransactionEntity); } - + @Test void toDto_shouldMapEntityValues_whenEntityHasValues() { Instant timestamp = Instant.now(); @@ -90,7 +83,7 @@ void toDto_shouldMapEntityValues_whenEntityHasValues() { assertThat(dto.getLabel()).hasToString("transaction test"); assertThat(dto.getMetadata()).usingRecursiveComparison().isEqualTo(metadata); } - + @Test void toEntity_shouldMapEntityValues_whenDtoHasValues() { Instant timestamp = Instant.now(); @@ -100,24 +93,23 @@ void toEntity_shouldMapEntityValues_whenDtoHasValues() { Map metadata = Map.of("amount_before", "0", "amount_after", "100"); BankAccountEntity emitterBankAccountEntity = createBankAccountEntity(emitterAccountId); BankAccountEntity receiverBankAccountEntity = createBankAccountEntity(receiverAccountId); - Transaction transaction = Transaction.builder() - .id(10L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal(100)) - .currency("EUR") - .type(TransactionType.CREDIT) - .status(TransactionStatus.COMPLETED) - .date(timestamp) - .label("transaction test") - .metadata(metadata) - .build(); + Transaction transaction = new Transaction(); + transaction.setId(10L); + transaction.setEmitterAccountId(emitterAccountId); + transaction.setReceiverAccountId(receiverAccountId); + transaction.setAmount(new BigDecimal(100)); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.CREDIT); + transaction.setStatus(TransactionStatus.COMPLETED); + transaction.setDate(timestamp); + transaction.setLabel("transaction test"); + transaction.setMetadata(metadata); when(bankAccountRepository.findByIdOptional(99L)).thenReturn(Optional.of(emitterBankAccountEntity)); when(bankAccountRepository.findByIdOptional(77L)).thenReturn(Optional.of(receiverBankAccountEntity)); - + TransactionEntity entity = transactionMapper.toEntity(transaction); - + assertThat(entity.getId()).isEqualTo(10L); assertThat(entity.getEmitterBankAccountEntity()).usingRecursiveComparison().isEqualTo(emitterBankAccountEntity); assertThat(entity.getReceiverBankAccountEntity()).usingRecursiveComparison().isEqualTo(receiverBankAccountEntity); diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionRepositoryTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionRepositoryTest.java index e7efa22e..0c482359 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionRepositoryTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionRepositoryTest.java @@ -1,6 +1,8 @@ package com.cdx.bas.application.bank.transaction; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; import io.quarkus.test.junit.QuarkusTest; @@ -17,10 +19,9 @@ import java.time.ZoneOffset; import java.util.*; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.COMPLETED; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.CREDIT; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.DEBIT; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.CREDIT; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.DEBIT; import static org.assertj.core.api.Assertions.assertThat; @@ -34,22 +35,28 @@ class TransactionRepositoryTest { @Test @Order(1) - public void findById_shouldFindTransaction_whenIdIsFound() { + void findById_shouldFindTransaction_whenIdIsFound() { Instant expectedInstant = OffsetDateTime.of( 2024, 6, 6, 12, 0, 0, 0, ZoneOffset.ofHours(1)).toInstant(); - Optional expectedTransaction = Optional.of(Transaction.builder() - .id(1L) - .emitterAccountId(1L) - .receiverAccountId(2L) - .amount(new BigDecimal("1600.00")) - .currency("EUR") - .type(CREDIT) - .status(COMPLETED) - .date(expectedInstant) - .label("transaction 1") - .metadata(Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")) - .build()); + Transaction transaction = new Transaction(); + transaction.setId(1L); + transaction.setEmitterAccountId(1L); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("1600.00")); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.CREDIT); + transaction.setStatus(TransactionStatus.COMPLETED); + transaction.setDate(expectedInstant); + transaction.setLabel("transaction 1"); + transaction.setMetadata(Map.of( + "emitter_amount_before", "2000", + "receiver_amount_before", "0", + "emitter_amount_after", "400", + "receiver_amount_after", "1600" + )); + + Optional expectedTransaction = Optional.of(transaction); Optional actualTransaction = transactionRepository.findById(1); // Assert @@ -60,7 +67,7 @@ public void findById_shouldFindTransaction_whenIdIsFound() { @Test @Order(2) - public void findUnprocessedTransactions_shouldFindEveryUnprocessedTransactions() { + void findUnprocessedTransactions_shouldFindEveryUnprocessedTransactions() { Queue actualUnprocessedTransactions = transactionRepository.findUnprocessedTransactions(); Queue expectedUnprocessedTransactions = new PriorityQueue<>(); @@ -79,35 +86,31 @@ public void findUnprocessedTransactions_shouldFindEveryUnprocessedTransactions() @Test @Order(3) @Transactional - public void create_shouldPersistTransaction() { + void create_shouldPersistTransaction() { + long id = 20L; Transaction transactionToCreate = new Transaction(null, 8L, 1L, new BigDecimal("99999.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 8", new HashMap<>()); transactionRepository.create(transactionToCreate); - transactionRepository.getEntityManager().flush(); - Optional actualOptionalTransaction = transactionRepository.findById(10L); + Optional actualOptionalTransaction = transactionRepository.findById(id); - - Transaction expectedTransaction = new Transaction(10L, 8L, 1L, new BigDecimal("99999.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 8", new HashMap<>()); - Optional optionalTransaction = Optional.of(expectedTransaction); - assertThat(transactionToCreate) - .usingRecursiveComparison() - .isEqualTo(transactionToCreate); - assertThat(actualOptionalTransaction) + Transaction expectedTransaction = new Transaction(id, 8L, 1L, new BigDecimal("99999.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 8", new HashMap<>()); + assertThat(actualOptionalTransaction).isNotEmpty(); + assertThat(actualOptionalTransaction.get()) .usingRecursiveComparison() - .isEqualTo(optionalTransaction); + .isEqualTo(expectedTransaction); } @Test @Order(4) @Transactional - public void update_shouldMergeTransaction() { + void update_shouldMergeTransaction() { Transaction expectedTransaction = new Transaction(2L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, UNPROCESSED, Instant.parse("2024-11-10T15:00:00+02:00"), "transaction to process", Map.of("emitter_amount_before", "9200", "receiver_amount_before", "10000", "emitter_amount_after", "0", "receiver_amount_after", "19200")); Optional optionalExpectedTransaction = Optional.of(expectedTransaction); - Transaction updatedTransaction = transactionRepository.update(expectedTransaction); + Transaction updatedTransaction = transactionRepository.update(expectedTransaction); Optional actualOptionalTransaction = transactionRepository.findById(2); assertThat(updatedTransaction) @@ -121,14 +124,17 @@ public void update_shouldMergeTransaction() { @Test @Order(5) @Transactional - public void deleteById_shouldDeleteTransaction_whenIdIsFound() { - long transactionIdToDelete = 10L; - Transaction transactionToDelete = new Transaction(10L, 8L, 1L, new BigDecimal("99999.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 8", new HashMap<>()); + void deleteById_shouldDeleteTransaction_whenIdIsFound() { + // Arrange + long id = 20L; + Transaction transactionToDelete = new Transaction(id, 8L, 1L, new BigDecimal("99999.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 8", new HashMap<>()); Optional optionalTransaction = Optional.of(transactionToDelete); + // Act + Optional deletedTransaction = transactionRepository.deleteById(id); - Optional deletedTransaction = transactionRepository.deleteById(transactionIdToDelete); - Optional deletedOptionalTransaction = transactionRepository.findById(10L); + // Assert + Optional deletedOptionalTransaction = transactionRepository.findById(id); assertThat(deletedOptionalTransaction).isEmpty(); assertThat(deletedTransaction) 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 index 6ac00100..e119eaa1 100644 --- 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 @@ -1,77 +1,99 @@ package com.cdx.bas.application.bank.transaction; +import com.cdx.bas.application.bank.transaction.category.cash.type.deposit.DepositProcessorImpl; +import com.cdx.bas.application.bank.transaction.category.cash.type.withdraw.WithdrawProcessorImpl; +import com.cdx.bas.application.bank.transaction.category.digital.type.credit.CreditProcessorImpl; +import com.cdx.bas.application.bank.transaction.category.digital.type.debit.DebitProcessorImpl; import com.cdx.bas.domain.bank.transaction.*; -import com.cdx.bas.domain.bank.transaction.type.TransactionProcessorServicePort; -import com.cdx.bas.domain.bank.transaction.validation.validator.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.junit.jupiter.api.TestMethodOrder; import java.math.BigDecimal; -import java.time.Clock; import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.COMPLETED; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.CREDIT; +import java.util.*; + +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.*; +import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.*; @QuarkusTest @WithTestResource(H2DatabaseTestResource.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class TransactionServiceTest { - Clock clock = Mockito.mock(Clock.class); - TransactionPersistencePort transactionRepository = Mockito.mock(TransactionPersistencePort.class); - TransactionProcessorServicePort transactionProcessorServicePort = Mockito.mock(TransactionProcessorServicePort.class); - TransactionValidator transactionValidator = Mockito.mock(TransactionValidator.class); + @Inject + TransactionPersistencePort transactionRepository; + + @Inject + TransactionValidator transactionValidator; + + @Inject + CreditProcessorImpl creditProcessorService; + + @Inject + DebitProcessorImpl debitProcessorService; - TransactionServiceImpl transactionService = new TransactionServiceImpl(transactionRepository, transactionValidator, transactionProcessorServicePort); + @Inject + DepositProcessorImpl depositProcessorService; + @Inject + WithdrawProcessorImpl withdrawProcessorService; + + @Inject + TransactionServicePort transactionService; @Test + @Order(1) void shouldReturnAllTransactions_whenRepositoryReturnsTransactions() { // Arrange - Set transactions = Set.of( - Transaction.builder().id(1L).build(), - Transaction.builder().id(2L).build(), - Transaction.builder().id(3L).build() + Set expectedTransactions = Set.of( + new Transaction(1L, 1L, 2L, new BigDecimal("1600.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-06-06T11:00:00+00:00"), "transaction 1", Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")), + new Transaction(2L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, ERROR, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 2", Map.of("error", "Transaction 2 deposit error for amount 9200 ...")), + new Transaction(3L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 3", Map.of("emitter_amount_before", "9200", "receiver_amount_before", "10000", "emitter_amount_after", "0", "receiver_amount_after", "19200")), + new Transaction(4L, 5L, 1L, new BigDecimal("100000.00"), "EUR", CREDIT, REFUSED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 4", Map.of("error", "Transaction 4 deposit error for amount 100000 ...")), + new Transaction(5L, 2L, 1L, new BigDecimal("600.99"), "EUR", CREDIT, UNPROCESSED, Instant.parse("2024-11-06T17:00:00+00:00"), "transaction 5", new HashMap<>()), + new Transaction(6L, 1L, 7L, new BigDecimal("2000.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-11-06T17:30:00+00:00"), "transaction 6", new HashMap<>()), + new Transaction(7L, 3L, 1L, new BigDecimal("1000.00"), "EUR", CREDIT, UNPROCESSED, Instant.parse("2024-12-06T17:00:00+00:00"), "transaction 7", new HashMap<>()), + new Transaction(8L, 4L, 2L, new BigDecimal("300.80"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:00+00:00"), "transaction 8", new HashMap<>()), + new Transaction(9L, 8L, 7L, new BigDecimal("5000.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 9", new HashMap<>()), + new Transaction(10L, 1L, null, new BigDecimal("100.00"), "EUR", DEPOSIT, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 10", new HashMap<>()), + new Transaction(11L, 1L, null, new BigDecimal("200.00"), "EUR", WITHDRAW, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 11", new HashMap<>()) ); - when(transactionRepository.getAll()).thenReturn(transactions); // Act - Set actual = transactionService.getAll(); + Set actualTransactions = transactionService.getAll(); // Assert - assertThat(actual).isEqualTo(transactions); - verify(transactionRepository).getAll(); - verifyNoMoreInteractions(transactionRepository); + assertThat(actualTransactions).isEqualTo(expectedTransactions); } @Test + @Order(2) void shouldReturnTransactionCorrespondingToStatus_whenStatusIsValid() { // Arrange - 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() + Set expectedTransactions = Set.of( + new Transaction(1L, 1L, 2L, new BigDecimal("1600.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-06-06T11:00:00+00:00"), "transaction 1", Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")), + new Transaction(3L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 3", Map.of("emitter_amount_before", "9200", "receiver_amount_before", "10000", "emitter_amount_after", "0", "receiver_amount_after", "19200")), + new Transaction(10L, 1L, null, new BigDecimal("100.00"), "EUR", DEPOSIT, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 10", new HashMap<>()), + new Transaction(11L, 1L, null, new BigDecimal("200.00"), "EUR", WITHDRAW, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 11", new HashMap<>()) ); - when(transactionRepository.findAllByStatus(COMPLETED)).thenReturn(transactions); // Act - Set actual = transactionService.findAllByStatus("COMPLETED"); + Set actualTransactions = transactionService.findAllByStatus("COMPLETED"); // Assert - assertThat(actual).isEqualTo(transactions); - verify(transactionRepository).findAllByStatus(COMPLETED); - verifyNoMoreInteractions(transactionRepository); + assertThat(actualTransactions).isEqualTo(expectedTransactions); } @Test @@ -83,122 +105,124 @@ void shouldThrowException_whenStatusIsInvalid() { // Assert assertThat(exception.getMessage()).isEqualTo("Invalid status: INVALID"); } - verifyNoInteractions(transactionRepository); } @Test void shouldCreateTransaction_whenNewTransactionIsValid() { // Arrange - Instant timestamp = Instant.parse("2024-03-14T12:00:00Z"); - NewDigitalTransaction newTransaction = new NewDigitalTransaction(99L, 77L, + long id = 20L; + NewDigitalTransaction newTransaction = new NewDigitalTransaction(1L, 2L, new BigDecimal("100"), "EUR", CREDIT, "transaction test", new HashMap<>()); - Transaction transactionToCreate = Transaction.builder() - .id(null) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("100")) - .currency("EUR") - .label("transaction test") - .type(CREDIT) - .status(UNPROCESSED) - .date(timestamp) - .metadata(new HashMap<>()) - .build(); - when(clock.instant()).thenReturn(timestamp); // Act transactionService.createDigitalTransaction(newTransaction); // Assert - verify(transactionValidator).validateNewDigitalTransaction(any()); - verify(transactionRepository).create(any()); - verifyNoMoreInteractions(transactionValidator, transactionRepository); + Optional optionalTransaction = transactionRepository.findById(id); + assertThat(optionalTransaction).isPresent(); + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setId(id); + expectedTransaction.setEmitterAccountId(1L); + expectedTransaction.setReceiverAccountId(2L); + expectedTransaction.setAmount(new BigDecimal("100.00")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setLabel("transaction test"); + expectedTransaction.setType(TransactionType.CREDIT); + expectedTransaction.setStatus(TransactionStatus.UNPROCESSED); + expectedTransaction.setMetadata(new HashMap<>()); + assertThat(optionalTransaction.get()) + .usingRecursiveComparison() + .ignoringFields("date") + .isEqualTo(expectedTransaction); } @Test void shouldThrowException_whenNewTransactionIsInvalid() { // Arrange - Instant timestamp = Instant.parse("2024-03-14T12:00:00Z"); + Instant timestamp = Instant.parse("2024-03-14T12:00:10+00:00"); Transaction invalidTransaction = new Transaction(); invalidTransaction.setDate(timestamp); invalidTransaction.setStatus(UNPROCESSED); invalidTransaction.setMetadata(null); - when(clock.instant()).thenReturn(timestamp); - doThrow(new TransactionException("invalid transaction...")).when(transactionValidator).validateNewDigitalTransaction(any()); - try { // Act transactionService.createDigitalTransaction(new NewDigitalTransaction(null, null, null, null, null, null, null)); - fail("should throw exception"); + fail("should throw exception"); } catch (TransactionException exception) { // Assert - assertThat(exception.getMessage()).isEqualTo("invalid transaction..."); - verify(transactionValidator).validateNewDigitalTransaction(any()); - verifyNoMoreInteractions(transactionValidator); - verifyNoInteractions(transactionRepository); + List expectedErrors = List.of("Amount must not be null.", + "Metadata must not be null.", + "Emitter account id must not be null.", + "Currency must not be null.", + "Label must not be null.", + "Type must not be null.", + "Receiver account id must not be null."); + List actualErrorLines = List.of(exception.getMessage().split("\n")); + assertThat(actualErrorLines).containsExactlyInAnyOrderElementsOf(expectedErrors); } } @Test void shouldFindTransaction_whenTransactionExists() { // Arrange - Transaction transaction = Transaction.builder() - .id(1L) - .amount(new BigDecimal(100)) - .emitterAccountId(100L) - .receiverAccountId(200L) - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("deposit of 100 euros") - .build(); - when(transactionRepository.findById(1L)).thenReturn(Optional.of(transaction)); + Transaction transaction = new Transaction(); + transaction.setId(1L); + transaction.setAmount(new BigDecimal("1600.00")); + transaction.setDate(Instant.parse("2024-06-06T11:00:00+00:00")); + transaction.setEmitterAccountId(1L); + transaction.setReceiverAccountId(2L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.CREDIT); + transaction.setStatus(COMPLETED); + transaction.setLabel("transaction 1"); + transaction.setMetadata(Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")); // Act Transaction actualTransaction = transactionService.findTransaction(1L); // Assert - assertThat(actualTransaction).isEqualTo(transaction); - verify(transactionRepository).findById(1L); - verifyNoMoreInteractions(transactionRepository); + assertThat(actualTransaction) + .usingRecursiveComparison() + .isEqualTo(transaction); } @Test void shouldReturnNull_whenTransactionDoesNotExist() { - // Arrange - when(transactionRepository.findById(1L)).thenReturn(Optional.empty()); - - // Act - Transaction actualTransaction = transactionService.findTransaction(1L); - - // Assert - assertThat(actualTransaction).isNull(); - verify(transactionRepository).findById(1L); - verifyNoMoreInteractions(transactionRepository); + try { + // Act + transactionService.findTransaction(99L); + } catch (TransactionException transactionException) { + // Assert + assertThat(transactionException.getMessage()).isEqualTo("Transaction: searching failed - not found\n" + + "Transaction id:99"); + } } @Test void shouldProcessBankAccountCredit_whenTransactionHasCreditType() { // Arrange - Transaction transaction = Transaction.builder() - .id(1L) - .amount(new BigDecimal(100)) - .emitterAccountId(100L) - .receiverAccountId(200L) - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("deposit of 100 euros") - .build(); + long id = 1L; + Transaction transaction = new Transaction(); + transaction.setId(id); + transaction.setAmount(new BigDecimal("100.00")); + transaction.setEmitterAccountId(id); + transaction.setReceiverAccountId(2L); + transaction.setType(TransactionType.CREDIT); + transaction.setStatus(TransactionStatus.UNPROCESSED); + transaction.setCurrency("EUR"); + transaction.setDate(Instant.now()); + transaction.setLabel("deposit of 100 euros"); // Act transactionService.processDigitalTransaction(transaction); // Assert - verify(transactionProcessorServicePort).credit(transaction); - verifyNoMoreInteractions(transactionProcessorServicePort); + Optional optionalTransaction = transactionRepository.findById(id); + assertThat(optionalTransaction).isPresent(); + assertThat(optionalTransaction) + .contains(transaction); } @Test @@ -206,40 +230,32 @@ void shouldProcessBankAccountDeposit_whenValidTransactionDeposit() { // Arrange Map metadata = new HashMap<>(); metadata.put("bill", "50,25,25"); - NewCashTransaction newTransaction = new NewCashTransaction(100L, new BigDecimal("100"), "EUR", metadata); - Instant timestamp = Instant.parse("2024-03-14T12:00:00Z"); - when(clock.instant()).thenReturn(timestamp); + NewCashTransaction newTransaction = new NewCashTransaction(1L, new BigDecimal("100"), "EUR", metadata); // Act transactionService.deposit(newTransaction); // Assert - verify(transactionProcessorServicePort).deposit(any()); - verifyNoMoreInteractions(transactionProcessorServicePort); + assertThat(transactionRepository.findById(20L)).isPresent(); } @Test void shouldThrowValidationException_whenInvalidTransactionDeposit() { // Arrange NewCashTransaction newTransaction = new NewCashTransaction(null, null, null, null); - doThrow(new TransactionException(""" - Amount must not be null. - Metadata must not be null. - Emitter account id must not be null. - Currency must not be null. - """)) - .when(transactionValidator).validateCashTransaction(any()); // Act try { transactionService.deposit(newTransaction); } catch (TransactionException exception) { - assertThat(exception.getMessage()).isEqualTo(""" - Amount must not be null. - Metadata must not be null. - Emitter account id must not be null. - Currency must not be null. - """); + List expectedErrors = List.of("Deposit transaction: deposit refused - domain error", + "Transaction id:null", + "Error:Amount must not be null.", + "Bill must be define for cash movements.", + "Emitter account id must not be null.", + "Currency must not be null."); + List actualErrorLines = List.of(exception.getMessage().split("\n")); + assertThat(actualErrorLines).containsExactlyInAnyOrderElementsOf(expectedErrors); } } @@ -248,15 +264,12 @@ void shouldProcessBankAccountWithdraw_whenValidTransactionWithdraw() { // Arrange Map metadata = new HashMap<>(); metadata.put("bill", "50,25,25"); - NewCashTransaction newTransaction = new NewCashTransaction(100L, new BigDecimal("100"), "EUR", metadata); - Instant timestamp = Instant.parse("2024-03-14T12:00:00Z"); - when(clock.instant()).thenReturn(timestamp); + NewCashTransaction newTransaction = new NewCashTransaction(1L, new BigDecimal("100"), "EUR", metadata); // Act transactionService.withdraw(newTransaction); // Assert - verify(transactionProcessorServicePort).withdraw(any()); - verifyNoMoreInteractions(transactionProcessorServicePort); + assertThat(transactionRepository.findById(20L)).isPresent(); } } diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionStatusServiceTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionStatusServiceTest.java index 02bc4559..15d6f789 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionStatusServiceTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionStatusServiceTest.java @@ -4,7 +4,7 @@ import com.cdx.bas.domain.bank.transaction.TransactionException; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; import io.quarkus.test.junit.QuarkusTest; @@ -37,31 +37,29 @@ class TransactionStatusServiceTest { @Test @Transactional void setAsOutstanding_shouldSetStatusAndUpdate_whenStatusIsOutstanding() { - Transaction transaction = Transaction.builder() - .id(9L) - .emitterAccountId(8L) - .receiverAccountId(7L) - .type(TransactionType.DEBIT) - .amount(new BigDecimal("5000.00")) - .currency("EUR") - .status(TransactionStatus.UNPROCESSED) - .date(OffsetDateTime.parse("2024-12-06T19:00:10+01:00").toInstant()) - .label("transaction 9") - .metadata(Map.of()) - .build(); - - Transaction expectedTransaction = Transaction.builder() - .id(9L) - .emitterAccountId(8L) - .receiverAccountId(7L) - .type(TransactionType.DEBIT) - .amount(new BigDecimal("5000.00")) - .currency("EUR") - .status(TransactionStatus.OUTSTANDING) - .date(OffsetDateTime.parse("2024-12-06T19:00:10+01:00").toInstant()) - .label("transaction 9") - .metadata(Map.of()) - .build(); + Transaction transaction = new Transaction(); + transaction.setId(9L); + transaction.setEmitterAccountId(8L); + transaction.setReceiverAccountId(7L); + transaction.setType(TransactionType.DEBIT); + transaction.setAmount(new BigDecimal("5000.00")); + transaction.setCurrency("EUR"); + transaction.setStatus(TransactionStatus.UNPROCESSED); + transaction.setDate(OffsetDateTime.parse("2024-12-06T19:00:10+01:00").toInstant()); + transaction.setLabel("transaction 9"); + transaction.setMetadata(Map.of()); + + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setId(9L); + expectedTransaction.setEmitterAccountId(8L); + expectedTransaction.setReceiverAccountId(7L); + expectedTransaction.setType(TransactionType.DEBIT); + expectedTransaction.setAmount(new BigDecimal("5000.00")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setStatus(TransactionStatus.OUTSTANDING); + expectedTransaction.setDate(OffsetDateTime.parse("2024-12-06T19:00:10+01:00").toInstant()); + expectedTransaction.setLabel("transaction 9"); + expectedTransaction.setMetadata(Map.of()); Transaction actualTransaction = transactionStatusServicePort.setAsOutstanding(transaction); assertThat(actualTransaction) @@ -72,9 +70,8 @@ void setAsOutstanding_shouldSetStatusAndUpdate_whenStatusIsOutstanding() { @Test @Transactional void setAsOutstanding_shouldThrowTransactionException_whenStatusIsNotOutstanding() { - Transaction transaction = Transaction.builder() - .status(ERROR) - .build(); + Transaction transaction = new Transaction(); + transaction.setStatus(TransactionStatus.ERROR); try { transactionStatusServicePort.setAsOutstanding(transaction); @@ -88,36 +85,34 @@ void setAsOutstanding_shouldThrowTransactionException_whenStatusIsNotOutstanding @Transactional void setStatus_shouldSetStatusAndUpdate_whenTransactionIsValid() { // Arrange - Transaction transaction = Transaction.builder() - .id(7L) - .emitterAccountId(3L) - .receiverAccountId(1L) - .type(TransactionType.CREDIT) - .amount(new BigDecimal("1000.00")) - .currency("EUR") - .status(TransactionStatus.UNPROCESSED) - .date(OffsetDateTime.parse("2024-12-06T18:00:00+01:00").toInstant()) // Convert OffsetDateTime to Instant - .label("transaction 7") - .metadata(Map.of()) - .build(); + Transaction transaction = new Transaction(); + transaction.setId(7L); + transaction.setEmitterAccountId(3L); + transaction.setReceiverAccountId(1L); + transaction.setType(TransactionType.CREDIT); + transaction.setAmount(new BigDecimal("1000.00")); + transaction.setCurrency("EUR"); + transaction.setStatus(TransactionStatus.UNPROCESSED); + transaction.setDate(OffsetDateTime.parse("2024-12-06T18:00:00+01:00").toInstant()); // Convert OffsetDateTime to Instant + transaction.setLabel("transaction 7"); + transaction.setMetadata(new HashMap<>()); // or `Map.of()` if an immutable map is fine Map metadata = Map.of("error", "Transaction 1 deposit error for amount 100: error"); // Act Transaction actualTransaction = transactionStatusServicePort.saveStatus(transaction, ERROR, metadata); // Assert - Transaction expectedTransaction = Transaction.builder() - .id(7L) - .emitterAccountId(3L) - .receiverAccountId(1L) - .type(TransactionType.CREDIT) - .amount(new BigDecimal("1000.00")) - .currency("EUR") - .status(TransactionStatus.ERROR) - .date(OffsetDateTime.parse("2024-12-06T18:00:00+01:00").toInstant()) // Convert OffsetDateTime to Instant - .label("transaction 7") - .metadata(metadata) - .build(); + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setId(7L); + expectedTransaction.setEmitterAccountId(3L); + expectedTransaction.setReceiverAccountId(1L); + expectedTransaction.setType(TransactionType.CREDIT); + expectedTransaction.setAmount(new BigDecimal("1000.00")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setStatus(TransactionStatus.ERROR); + expectedTransaction.setDate(OffsetDateTime.parse("2024-12-06T18:00:00+01:00").toInstant()); // Convert OffsetDateTime to Instant + expectedTransaction.setLabel("transaction 7"); + expectedTransaction.setMetadata(metadata); // Assuming 'metadata' is a Map variable defined elsewhere assertThat(actualTransaction) .usingRecursiveComparison() diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionUtilsTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionUtilsTest.java index aff15377..c50cbc78 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionUtilsTest.java +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionUtilsTest.java @@ -1,9 +1,10 @@ package com.cdx.bas.application.bank.transaction; -import com.cdx.bas.domain.bank.transaction.NewCashTransaction; -import com.cdx.bas.domain.bank.transaction.NewDigitalTransaction; import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -11,13 +12,10 @@ import java.math.BigDecimal; import java.time.Instant; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.DEBIT; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.CREDIT; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.DEBIT; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) @@ -40,16 +38,16 @@ void should_getNewDigitalTransaction_when_hasTransactioNewDigitalTransaction() { Transaction actualDigitalTransaction = TransactionUtils.getNewDigitalTransaction(newDigitalTransaction); // Assert - Transaction expectedDigitalTransaction = Transaction.builder() - .emitterAccountId(1L) - .receiverAccountId(2L) - .amount(new BigDecimal("100")) - .currency("USD") - .type(CREDIT) - .status(UNPROCESSED) - .label("credit transaction") - .metadata(new HashMap<>()) - .build(); + Transaction expectedDigitalTransaction = new Transaction(); + expectedDigitalTransaction.setEmitterAccountId(1L); + expectedDigitalTransaction.setReceiverAccountId(2L); + expectedDigitalTransaction.setAmount(new BigDecimal("100")); + expectedDigitalTransaction.setCurrency("USD"); + expectedDigitalTransaction.setType(TransactionType.CREDIT); + expectedDigitalTransaction.setStatus(TransactionStatus.UNPROCESSED); + expectedDigitalTransaction.setLabel("credit transaction"); + expectedDigitalTransaction.setMetadata(new HashMap<>()); + assertThat(actualDigitalTransaction) .usingRecursiveComparison() .ignoringFieldsOfTypes(Instant.class) @@ -73,13 +71,12 @@ void should_getNewCashTransaction_when_hasTransactioNewDigitalTransaction() { Transaction actualCashTransaction = TransactionUtils.getNewCashTransaction(newDigitalTransaction); // Assert - Transaction expectedCashTransaction = Transaction.builder() - .emitterAccountId(1L) - .amount(new BigDecimal("100")) - .currency("USD") - .status(UNPROCESSED) - .metadata(new HashMap<>()) - .build(); + Transaction expectedCashTransaction = new Transaction(); + expectedCashTransaction.setEmitterAccountId(1L); + expectedCashTransaction.setAmount(new BigDecimal("100")); + expectedCashTransaction.setCurrency("USD"); + expectedCashTransaction.setStatus(TransactionStatus.UNPROCESSED); + expectedCashTransaction.setMetadata(new HashMap<>()); assertThat(actualCashTransaction) .usingRecursiveComparison() .ignoringFieldsOfTypes(Instant.class) @@ -92,30 +89,28 @@ void should_getNewCashTransaction_when_hasTransactioNewDigitalTransaction() { @Test void shouldMergeOldTransactionWithNewTransaction_whenOldTransactionAndNewTransactionAreValid() { // Arrange - Transaction oldTransaction = Transaction.builder() - .id(1L) - .amount(new BigDecimal(100)) - .emitterAccountId(10L) - .receiverAccountId(11L) - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("old transaction") - .build(); + Transaction oldTransaction = new Transaction(); + oldTransaction.setId(1L); + oldTransaction.setAmount(new BigDecimal(100)); + oldTransaction.setEmitterAccountId(10L); + oldTransaction.setReceiverAccountId(11L); + oldTransaction.setType(TransactionType.CREDIT); + oldTransaction.setStatus(TransactionStatus.UNPROCESSED); + oldTransaction.setDate(Instant.now()); + oldTransaction.setLabel("old transaction"); Instant dateAfter = Instant.now(); BigDecimal bigDecimalAfter = new BigDecimal(200); String labelAfter = "new transaction"; - Transaction newTransaction = Transaction.builder() - .id(2L) - .amount(bigDecimalAfter) - .emitterAccountId(20L) - .receiverAccountId(22L) - .type(DEBIT) - .status(UNPROCESSED) - .date(dateAfter) - .label(labelAfter) - .build(); + Transaction newTransaction = new Transaction(); + newTransaction.setId(2L); + newTransaction.setAmount(bigDecimalAfter); + newTransaction.setEmitterAccountId(20L); + newTransaction.setReceiverAccountId(22L); + newTransaction.setType(TransactionType.DEBIT); + newTransaction.setStatus(TransactionStatus.UNPROCESSED); + newTransaction.setDate(dateAfter); + newTransaction.setLabel(labelAfter); // Act Transaction actualTransaction = TransactionUtils.mergeTransactions(oldTransaction, newTransaction); @@ -132,18 +127,4 @@ void shouldMergeOldTransactionWithNewTransaction_whenOldTransactionAndNewTransac // Assert assertThat(actualTransaction).isEqualTo(oldTransaction); } - - @Test - public void test() { - Transaction t1 = Transaction.builder().build(); - Transaction t2 = Transaction.builder().build(); - - Set transactions = new HashSet(); - - transactions.add(t1); - transactions.add(t2); - - System.out.println(transactions); - } - } \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorServiceImplTest.java new file mode 100644 index 00000000..a8ff3628 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/CreditProcessorServiceImplTest.java @@ -0,0 +1,88 @@ +package com.cdx.bas.application.bank.transaction.type; + +import com.cdx.bas.application.bank.transaction.category.digital.type.credit.CreditProcessorImpl; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.money.Money; +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +@WithTestResource(H2DatabaseTestResource.class) +class CreditProcessorServiceImplTest { + + @Inject + BankAccountServicePort bankAccountService; + + @Inject + CreditProcessorImpl creditProcessorService; + + @Test + @Transactional + void should_creditReceiverAndDebitEmitter_when_transactionIsValidated() { + // Arrange + long emitterAccountId = 2L; + long receiverAccountId = 1L; + Transaction transaction = new Transaction(); + transaction.setId(5L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.CREDIT); + transaction.setEmitterAccountId(emitterAccountId); + transaction.setReceiverAccountId(receiverAccountId); + transaction.setAmount(new BigDecimal("600.99")); + transaction.setStatus(TransactionStatus.UNPROCESSED); + ZonedDateTime dateTime = ZonedDateTime.of(2024, 11, 6, 18, 0, 0, 0, ZoneOffset.ofHours(1)); + Instant transactionDate = dateTime.toInstant(); + transaction.setDate(transactionDate); + transaction.setLabel("transaction 5"); + transaction.setMetadata(new HashMap<>()); + + // Act + Transaction actualTransaction = creditProcessorService.processTransaction(transaction); + + // Assert + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setId(5L); + expectedTransaction.setEmitterAccountId(2L); + expectedTransaction.setReceiverAccountId(1L); + expectedTransaction.setAmount(new BigDecimal("600.99")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setType(TransactionType.CREDIT); + expectedTransaction.setStatus(TransactionStatus.COMPLETED); + expectedTransaction.setDate(Instant.parse("2024-11-06T17:00:00Z")); + expectedTransaction.setLabel("transaction 5"); + + Map metadata = new HashMap<>(); + metadata.put("emitter_amount_after", "999.01"); + metadata.put("receiver_amount_before", "400.00"); + metadata.put("emitter_amount_before", "1600.00"); + metadata.put("receiver_amount_after", "1000.99"); + expectedTransaction.setMetadata(metadata); + + assertThat(actualTransaction).isEqualTo(expectedTransaction); + BankAccount emitterAccount = bankAccountService.findBankAccount(emitterAccountId); + assertThat(emitterAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("999.01"))); + BankAccount receiverAccount = bankAccountService.findBankAccount(receiverAccountId); + assertThat(receiverAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("1000.99"))); + } +} \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorServiceImplTest.java new file mode 100644 index 00000000..d050edf0 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/DebitProcessorServiceImplTest.java @@ -0,0 +1,90 @@ +package com.cdx.bas.application.bank.transaction.type; + +import com.cdx.bas.application.bank.transaction.category.digital.type.debit.DebitProcessorImpl; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; +import com.cdx.bas.domain.money.Money; +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +@WithTestResource(H2DatabaseTestResource.class) +class DebitProcessorServiceImplTest { + + @Inject + BankAccountServicePort bankAccountService; + + @Inject + DebitProcessorImpl debitProcessor; + + @Test + @Transactional + void should_debitReceiverAndDebitEmitter_when_transactionIsValidated() { + // Arrange + long emitterAccountId = 2L; + long receiverAccountId = 1L; + Transaction transaction = new Transaction(); + transaction.setId(5L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEBIT); + transaction.setEmitterAccountId(emitterAccountId); + transaction.setReceiverAccountId(receiverAccountId); + transaction.setAmount(new BigDecimal("400.00")); + transaction.setStatus(TransactionStatus.UNPROCESSED); + ZonedDateTime dateTime = ZonedDateTime.of(2024, 11, 6, 18, 0, 0, 0, ZoneOffset.ofHours(1)); + Instant transactionDate = dateTime.toInstant(); + transaction.setDate(transactionDate); + transaction.setLabel("transaction 5"); + transaction.setMetadata(new HashMap<>()); + + // Act + Transaction actualTransaction = debitProcessor.processTransaction(transaction); + + // Assert + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setId(5L); + expectedTransaction.setEmitterAccountId(2L); + expectedTransaction.setReceiverAccountId(1L); + expectedTransaction.setAmount(new BigDecimal("400.00")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setType(TransactionType.DEBIT); + expectedTransaction.setStatus(TransactionStatus.COMPLETED); + expectedTransaction.setDate(Instant.parse("2024-11-06T17:00:00Z")); + expectedTransaction.setLabel("transaction 5"); + + Map metadata = new HashMap<>(); + metadata.put("emitter_amount_after", "2000.00"); + metadata.put("receiver_amount_before", "400.00"); + metadata.put("emitter_amount_before", "1600.00"); + metadata.put("receiver_amount_after", "0.00"); + expectedTransaction.setMetadata(metadata); + + assertThat(actualTransaction) + .usingRecursiveComparison() + .isEqualTo(expectedTransaction); + BankAccount emitterAccount = bankAccountService.findBankAccount(emitterAccountId); + assertThat(emitterAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("2000.00"))); + BankAccount receiverAccount = bankAccountService.findBankAccount(receiverAccountId); + assertThat(receiverAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("0.00"))); + } +} \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorServiceImplTest.java new file mode 100644 index 00000000..ff910fd0 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/DepositProcessorServiceImplTest.java @@ -0,0 +1,80 @@ +package com.cdx.bas.application.bank.transaction.type; + +import com.cdx.bas.application.bank.transaction.category.cash.type.deposit.DepositProcessorImpl; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; +import com.cdx.bas.domain.money.Money; +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +@WithTestResource(H2DatabaseTestResource.class) +class DepositProcessorServiceImplTest { + + @Inject + BankAccountServicePort bankAccountService; + + @Inject + DepositProcessorImpl depositProcessor; + + @Test + @Transactional + void should_debitReceiverAndDebitEmitter_when_transactionIsValidated() { + // Arrange + long emitterAccountId = 2L; + Transaction transaction = new Transaction(); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEPOSIT); + transaction.setEmitterAccountId(emitterAccountId); + transaction.setAmount(new BigDecimal("1000.00")); + transaction.setStatus(TransactionStatus.UNPROCESSED); + ZonedDateTime dateTime = ZonedDateTime.of(2024, 11, 6, 18, 0, 0, 0, ZoneOffset.ofHours(1)); + Instant transactionDate = dateTime.toInstant(); + transaction.setDate(transactionDate); + transaction.setLabel("transaction 5"); + transaction.setMetadata(new HashMap<>()); + transaction.getMetadata().put("bill", "500,500"); + + // Act + Transaction actualTransaction = depositProcessor.processTransaction(transaction); + + // Assert + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setEmitterAccountId(2L); + expectedTransaction.setAmount(new BigDecimal("1000.00")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setType(TransactionType.DEPOSIT); + expectedTransaction.setStatus(TransactionStatus.COMPLETED); + expectedTransaction.setDate(Instant.parse("2024-11-06T17:00:00Z")); + expectedTransaction.setLabel("transaction 5"); + Map metadata = new HashMap<>(); + metadata.put("bill", "500,500"); + metadata.put("emitter_amount_after", "2600.00"); + metadata.put("emitter_amount_before", "1600.00"); + expectedTransaction.setMetadata(metadata); + + assertThat(actualTransaction) + .usingRecursiveComparison() + .isEqualTo(expectedTransaction); + BankAccount emitterAccount = bankAccountService.findBankAccount(emitterAccountId); + assertThat(emitterAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("2600.00"))); + } +} \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImplTest.java deleted file mode 100644 index 1c95273b..00000000 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/type/TransactionProcessorServiceImplTest.java +++ /dev/null @@ -1,502 +0,0 @@ -package com.cdx.bas.application.bank.transaction.type; - -import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.BankAccountException; -import com.cdx.bas.domain.bank.account.BankAccountServicePort; -import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; -import com.cdx.bas.domain.bank.transaction.status.TransactionStatusServicePort; -import com.cdx.bas.domain.bank.transaction.type.TransactionProcessorServicePort; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; -import com.cdx.bas.domain.money.Money; -import io.quarkus.test.InjectMock; -import io.quarkus.test.common.WithTestResource; -import io.quarkus.test.h2.H2DatabaseTestResource; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.math.BigDecimal; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; - -import static com.cdx.bas.domain.bank.account.type.AccountType.CHECKING; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -@QuarkusTest -@WithTestResource(H2DatabaseTestResource.class) -class TransactionProcessorServiceImplTest { - - @InjectMock - TransactionStatusServicePort transactionStatusService; - - @InjectMock - BankAccountServicePort bankAccountService; - - @Inject - TransactionProcessorServicePort transactionProcessingService; - - @Test - void credit_shouldThrowNoSuchElementException_whenEmitterAccountIsNotFound() { - // Arrange - long emitterAccountId = 99L; - long receiverAccountId = 77L; - Instant instantDate = Instant.now(); - Transaction transaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - Map metadata = Map.of("error", "Emitter bank account 99 is not found."); - Transaction erroredTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(ERROR) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - - when(bankAccountService.findBankAccount(emitterAccountId)).thenThrow(new NoSuchElementException("Emitter bank account 99 is not found.")); - when(transactionStatusService.saveStatus(eq(transaction), eq(ERROR), eq(metadata))).thenReturn(erroredTransaction); - - // Act - try { - transactionProcessingService.credit(transaction); - } catch (NoSuchElementException exception) { - // Assert - assertThat(exception.getMessage()).isEqualTo("Transaction does not have emitter bank account entity."); - verify(bankAccountService).findBankAccount(emitterAccountId); - verifyNoMoreInteractions(bankAccountService, transactionStatusService); - } - } - - @Test - void credit_shouldThrowNoSuchElementException_whenReceiverAccountIsNotFound() { - // Arrange - long emitterAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal("0")); - Instant instantDate = Instant.now(); - Transaction transaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - Map metadata = Map.of("error", "Receiver bank account 77 is not found."); - Transaction erroredTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(ERROR) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount emitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(transaction)) - .build(); - - when(bankAccountService.findBankAccount(emitterAccountId)).thenReturn(emitterBankAccount); - when(bankAccountService.findBankAccount(receiverAccountId)).thenThrow(new NoSuchElementException("Receiver bank account 77 is not found.")); - when(transactionStatusService.saveStatus(eq(transaction), eq(ERROR), eq(metadata))).thenReturn(erroredTransaction); - - // Act - try { - transactionProcessingService.credit(transaction); - } catch (NoSuchElementException exception) { - // Assert - assertThat(exception.getMessage()).isEqualTo("Transaction does not have emitter bank account entity."); - verify(bankAccountService).findBankAccount(emitterAccountId); - verify(bankAccountService).findBankAccount(receiverAccountId); - verifyNoMoreInteractions(bankAccountService, transactionStatusService); - } - } - - @Test - void credit_shouldReturnCompletedTransaction_whenAccountsAreFoundAndCreditTransactionIsValid() { - // Arrange - long emitterAccountId = 2L; - long receiverAccountId = 1L; - Money amountOfMoney = Mockito.mock(Money.class); - Instant instantDate = Instant.now(); - Transaction transaction = Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - Transaction outstandingTransaction = Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - BankAccount emitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(transaction)) - .build(); - BankAccount receiverBankAccount = CheckingBankAccount.builder() - .id(receiverAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .build(); - - Map metadataAfter = Map.of("emitter_amount_before", "1000", - "receiver_amount_before", "0", - "emitter_amount_after", "0", - "receiver_amount_after", "1000"); - Transaction completedTransaction = Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(COMPLETED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount updatedEmitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(completedTransaction)) - .build(); - - when(bankAccountService.findBankAccount(emitterAccountId)).thenReturn(emitterBankAccount); - when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(receiverBankAccount); - when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(transactionStatusService.setStatus(transaction, COMPLETED, metadataAfter)).thenReturn(completedTransaction); - when(amountOfMoney.getAmount()) - .thenReturn(new BigDecimal("1000")) - .thenReturn(new BigDecimal("0")) - .thenReturn(new BigDecimal("0")) - .thenReturn(new BigDecimal("1000")); - when(transactionStatusService.saveStatus(outstandingTransaction, COMPLETED, metadataAfter)).thenReturn(completedTransaction); - when(bankAccountService.putTransaction(completedTransaction, emitterBankAccount)).thenReturn(updatedEmitterBankAccount); - - // Act - Transaction returnedTransaction = transactionProcessingService.credit(transaction); - receiverBankAccount.getBalance().plus(emitterBankAccount.getBalance()); - emitterBankAccount.getBalance().minus(emitterBankAccount.getBalance()); - - // Assert - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(COMPLETED) - .date(instantDate) - .metadata(new HashMap<>()) - .build()); - } - - @Test - void credit_shouldAddMoneyToTheReceiverAccount_whenAccountsAreFound_fromCreditTransaction() { - // Arrange - long emitterAccountId = 2L; - long receiverAccountId = 1L; - Money amountOfMoney = Mockito.mock(Money.class); - Instant instantDate = Instant.now(); - Transaction oldTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount emitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(oldTransaction)) - .build(); - BankAccount receiverBankAccount = CheckingBankAccount.builder() - .id(receiverAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .build(); - - Transaction transaction = Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - Transaction outstandingTransaction = Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(OUTSTANDING) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - - Map metadataAfter = Map.of("emitter_amount_before", "1000", - "receiver_amount_before", "0", - "emitter_amount_after", "0", - "receiver_amount_after", "1000"); - Transaction completedTransaction = Transaction.builder() - .id(1L) - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(COMPLETED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount updatedEmitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(oldTransaction)) - .build(); - - when(bankAccountService.findBankAccount(emitterAccountId)).thenReturn(emitterBankAccount); - when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(receiverBankAccount); - when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(amountOfMoney.getAmount()) - .thenReturn(new BigDecimal("1000")) - .thenReturn(new BigDecimal("0")) - .thenReturn(new BigDecimal("0")) - .thenReturn(new BigDecimal("1000")); - when(transactionStatusService.saveStatus(outstandingTransaction, COMPLETED, metadataAfter)).thenReturn(completedTransaction); - when(bankAccountService.putTransaction(completedTransaction, emitterBankAccount)).thenReturn(updatedEmitterBankAccount); - - // Act - transactionProcessingService.credit(transaction); - - // Assert - verify(bankAccountService).findBankAccount(emitterAccountId); - verify(bankAccountService).findBankAccount(receiverAccountId); - verify(transactionStatusService).setAsOutstanding(transaction); - verify(bankAccountService).creditAmountToAccounts(outstandingTransaction, emitterBankAccount, receiverBankAccount); - verify(transactionStatusService).setStatus(transaction, COMPLETED, metadataAfter); - verify(bankAccountService).updateBankAccount(updatedEmitterBankAccount); - verify(bankAccountService).updateBankAccount(receiverBankAccount); - verifyNoMoreInteractions(bankAccountService, transactionStatusService); - } - - @Test - void credit_shouldReturnRefusedTransaction_whenTransferThrowTransactionExceptionException() { - // Arrange - long emitterAccountId = 1L; - long receiverAccountId = 2L; - Money amountOfMoney = Mockito.mock(Money.class); - Instant instantDate = Instant.now(); - Transaction oldTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("-1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount emitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(oldTransaction)) - .build(); - BankAccount receiverBankAccount = CheckingBankAccount.builder() - .id(receiverAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .build(); - - Transaction transaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("-1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - Transaction outstandingTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("-1000")) - .status(OUTSTANDING) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - - when(bankAccountService.findBankAccount(emitterAccountId)).thenReturn(emitterBankAccount); - when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(receiverBankAccount); - when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(amountOfMoney.getAmount()) - .thenReturn(new BigDecimal("1000")) - .thenReturn(new BigDecimal("0")); - doThrow(new TransactionException("Credit transaction 1 should have positive value, actual value: -1000")) - .when(bankAccountService).creditAmountToAccounts(outstandingTransaction, emitterBankAccount, receiverBankAccount); - - // Act - try { - transactionProcessingService.credit(transaction); - } catch (TransactionException transactionException) { - // Assert - verify(bankAccountService).findBankAccount(emitterAccountId); - verify(bankAccountService).findBankAccount(receiverAccountId); - verify(transactionStatusService).setAsOutstanding(transaction); - verify(bankAccountService).creditAmountToAccounts(outstandingTransaction, emitterBankAccount, receiverBankAccount); - verifyNoMoreInteractions(bankAccountService, transactionStatusService); - } - } - - @Test - void credit_shouldReturnErroredTransaction_whenAddTransactionThrowsBankAccountException() { - // Arrange - long emitterAccountId = 99L; - long receiverAccountId = 101L; - Money amountOfMoney = Mockito.mock(Money.class); - Instant instantDate = Instant.now(); - Transaction oldTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount emitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(oldTransaction)) - .build(); - BankAccount receiverBankAccount = CheckingBankAccount.builder() - .id(receiverAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .build(); - - Transaction transaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(UNPROCESSED) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - Transaction outstandingTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(OUTSTANDING) - .date(instantDate) - .metadata(new HashMap<>()) - .currency("EUR") - .label("transaction test") - .type(TransactionType.CREDIT) - .build(); - - Map metadataDuringProcess = Map.of("emitter_amount_before", "0", - "receiver_amount_before", "0", - "emitter_amount_after", "-1000", - "receiver_amount_after", "1000"); - Transaction completedTransaction = Transaction.builder() - .emitterAccountId(emitterAccountId) - .receiverAccountId(receiverAccountId) - .amount(new BigDecimal("1000")) - .status(COMPLETED) - .date(instantDate) - .metadata(new HashMap<>()) - .build(); - BankAccount updatedEmitterBankAccount = CheckingBankAccount.builder() - .id(emitterAccountId) - .type(CHECKING) - .balance(amountOfMoney) - .customersId(Set.of(99L)) - .issuedTransactions(Set.of(completedTransaction)) - .build(); - Map metadataAfterError = Map.of("error", "Amount of credit should not be negative"); - - when(bankAccountService.findBankAccount(emitterAccountId)).thenReturn(emitterBankAccount); - when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(receiverBankAccount); - when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(amountOfMoney.getAmount()) - .thenReturn(new BigDecimal("0")) - .thenReturn(new BigDecimal("0")) - .thenReturn(new BigDecimal("-1000")) - .thenReturn(new BigDecimal("1000")); - when(transactionStatusService.saveStatus(outstandingTransaction, COMPLETED, metadataDuringProcess)).thenReturn(completedTransaction); - when(bankAccountService.putTransaction(completedTransaction, emitterBankAccount)).thenReturn(updatedEmitterBankAccount); - doThrow(new BankAccountException("Amount of credit should not be negative")).when(bankAccountService).updateBankAccount(updatedEmitterBankAccount); - - // Act - try { - transactionProcessingService.credit(transaction); - } catch (NoSuchElementException exception) { - // Assert - verify(bankAccountService).findBankAccount(emitterAccountId); - verify(bankAccountService).findBankAccount(receiverAccountId); - verify(transactionStatusService).setAsOutstanding(transaction); - verify(bankAccountService).creditAmountToAccounts(outstandingTransaction, emitterBankAccount, receiverBankAccount); - verify(bankAccountService).updateBankAccount(updatedEmitterBankAccount); - verify(transactionStatusService).setStatus(transaction, COMPLETED, metadataDuringProcess); - verifyNoMoreInteractions(bankAccountService, transactionStatusService); - } - } -} \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/type/WithdrawProcessorServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/WithdrawProcessorServiceImplTest.java new file mode 100644 index 00000000..5d7f1193 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/bank/transaction/type/WithdrawProcessorServiceImplTest.java @@ -0,0 +1,80 @@ +package com.cdx.bas.application.bank.transaction.type; + +import com.cdx.bas.application.bank.transaction.category.cash.type.withdraw.WithdrawProcessorImpl; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; +import com.cdx.bas.domain.money.Money; +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +@WithTestResource(H2DatabaseTestResource.class) +class WithdrawProcessorServiceImplTest { + + @Inject + BankAccountServicePort bankAccountService; + + @Inject + WithdrawProcessorImpl withdrawProcessor; + + @Test + @Transactional + void should_debitReceiverAndDebitEmitter_when_transactionIsValidated() { + // Arrange + long emitterAccountId = 2L; + Transaction transaction = new Transaction(); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.WITHDRAW); + transaction.setEmitterAccountId(emitterAccountId); + transaction.setAmount(new BigDecimal("1000.00")); + transaction.setStatus(TransactionStatus.UNPROCESSED); + ZonedDateTime dateTime = ZonedDateTime.of(2024, 11, 6, 18, 0, 0, 0, ZoneOffset.ofHours(1)); + Instant transactionDate = dateTime.toInstant(); + transaction.setDate(transactionDate); + transaction.setLabel("transaction 5"); + transaction.setMetadata(new HashMap<>()); + transaction.getMetadata().put("bill", "500,500"); + + // Act + Transaction actualTransaction = withdrawProcessor.processTransaction(transaction); + + // Assert + Transaction expectedTransaction = new Transaction(); + expectedTransaction.setEmitterAccountId(2L); + expectedTransaction.setAmount(new BigDecimal("1000.00")); + expectedTransaction.setCurrency("EUR"); + expectedTransaction.setType(TransactionType.WITHDRAW); + expectedTransaction.setStatus(TransactionStatus.COMPLETED); + expectedTransaction.setDate(Instant.parse("2024-11-06T17:00:00Z")); + expectedTransaction.setLabel("transaction 5"); + Map metadata = new HashMap<>(); + metadata.put("bill", "500,500"); + metadata.put("emitter_amount_after", "600.00"); + metadata.put("emitter_amount_before", "1600.00"); + expectedTransaction.setMetadata(metadata); + + assertThat(actualTransaction) + .usingRecursiveComparison() + .isEqualTo(expectedTransaction); + BankAccount emitterAccount = bankAccountService.findBankAccount(emitterAccountId); + assertThat(emitterAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("600.00"))); + } +} \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java b/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java index eec93f8b..9077b5ea 100644 --- a/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java +++ b/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java @@ -3,7 +3,8 @@ import com.cdx.bas.domain.bank.transaction.Transaction; import com.cdx.bas.domain.bank.transaction.TransactionPersistencePort; import com.cdx.bas.domain.bank.transaction.TransactionServicePort; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import io.quarkus.test.InjectMock; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; @@ -22,7 +23,6 @@ import java.util.PriorityQueue; import java.util.Queue; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; @@ -83,61 +83,60 @@ void processQueue_shouldProcessTransactionsInCorrectOrder_whenQueueIsFilled() { private static Queue getQueue() { Queue queue = new PriorityQueue<>(Comparator.comparing(Transaction::getDate)); - - queue.add(Transaction.builder() - .id(1L) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("250")) - .label("First transaction") - .type(TransactionType.CREDIT) - .status(UNPROCESSED) - .date(Instant.parse("2022-12-08T00:00:00Z")) // Fixed date for consistency - .build()); - - queue.add(Transaction.builder() - .id(2L) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("399")) - .label("Second transaction") - .type(TransactionType.CREDIT) - .status(UNPROCESSED) - .date(Instant.parse("2022-12-07T10:14:00Z")) // Fixed date - .build()); - - queue.add(Transaction.builder() - .id(3L) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("150")) - .label("Third transaction") - .type(TransactionType.CREDIT) - .status(UNPROCESSED) - .date(Instant.parse("2022-12-06T10:14:00Z")) // Fixed date - .build()); - - queue.add(Transaction.builder() - .id(4L) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("1000")) - .label("Fourth transaction") - .type(TransactionType.CREDIT) - .status(UNPROCESSED) - .date(Instant.parse("2022-12-07T10:18:00Z")) // Fixed date - .build()); - - queue.add(Transaction.builder() - .id(5L) - .emitterAccountId(99L) - .receiverAccountId(77L) - .amount(new BigDecimal("59")) - .label("Fifth transaction") - .type(TransactionType.CREDIT) - .status(UNPROCESSED) - .date(Instant.MIN) // Oldest date for top priority - .build()); + Transaction transaction1 = new Transaction(); + transaction1.setId(1L); + transaction1.setEmitterAccountId(99L); + transaction1.setReceiverAccountId(77L); + transaction1.setAmount(new BigDecimal("250")); + transaction1.setLabel("First transaction"); + transaction1.setType(TransactionType.CREDIT); + transaction1.setStatus(TransactionStatus.UNPROCESSED); + transaction1.setDate(Instant.parse("2022-12-08T00:00:00Z")); + queue.add(transaction1); + + Transaction transaction2 = new Transaction(); + transaction2.setId(2L); + transaction2.setEmitterAccountId(99L); + transaction2.setReceiverAccountId(77L); + transaction2.setAmount(new BigDecimal("399")); + transaction2.setLabel("Second transaction"); + transaction2.setType(TransactionType.CREDIT); + transaction2.setStatus(TransactionStatus.UNPROCESSED); + transaction2.setDate(Instant.parse("2022-12-07T10:14:00Z")); + queue.add(transaction2); + + Transaction transaction3 = new Transaction(); + transaction3.setId(3L); + transaction3.setEmitterAccountId(99L); + transaction3.setReceiverAccountId(77L); + transaction3.setAmount(new BigDecimal("150")); + transaction3.setLabel("Third transaction"); + transaction3.setType(TransactionType.CREDIT); + transaction3.setStatus(TransactionStatus.UNPROCESSED); + transaction3.setDate(Instant.parse("2022-12-06T10:14:00Z")); + queue.add(transaction3); + + Transaction transaction4 = new Transaction(); + transaction4.setId(4L); + transaction4.setEmitterAccountId(99L); + transaction4.setReceiverAccountId(77L); + transaction4.setAmount(new BigDecimal("1000")); + transaction4.setLabel("Fourth transaction"); + transaction4.setType(TransactionType.CREDIT); + transaction4.setStatus(TransactionStatus.UNPROCESSED); + transaction4.setDate(Instant.parse("2022-12-07T10:18:00Z")); + queue.add(transaction4); + + Transaction transaction5 = new Transaction(); + transaction5.setId(5L); + transaction5.setEmitterAccountId(99L); + transaction5.setReceiverAccountId(77L); + transaction5.setAmount(new BigDecimal("59")); + transaction5.setLabel("Fifth transaction"); + transaction5.setType(TransactionType.CREDIT); + transaction5.setStatus(TransactionStatus.UNPROCESSED); + transaction5.setDate(Instant.MIN); // Oldest date for top priority + queue.add(transaction5); return queue; } } 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 6e564a25..3468449b 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 @@ -17,9 +17,13 @@ @RequestScoped public class BankAccountResource implements BankAccountControllerPort { - @Inject BankAccountServicePort bankAccountServicePort; + @Inject + public BankAccountResource(BankAccountServicePort bankAccountServicePort) { + this.bankAccountServicePort = bankAccountServicePort; + } + @GET @Produces(MediaType.APPLICATION_JSON) @Override diff --git a/client/src/main/java/com/cdx/bas/client/bank/customer/CustomerResource.java b/client/src/main/java/com/cdx/bas/client/bank/customer/CustomerResource.java index 7cc630f0..2cbd6724 100644 --- a/client/src/main/java/com/cdx/bas/client/bank/customer/CustomerResource.java +++ b/client/src/main/java/com/cdx/bas/client/bank/customer/CustomerResource.java @@ -2,18 +2,15 @@ import com.cdx.bas.domain.bank.customer.Customer; -import com.cdx.bas.domain.bank.customer.CustomerPersistencePort; import com.cdx.bas.domain.bank.customer.CustomerServicePort; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.transaction.Transactional; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import java.util.Optional; import java.util.Set; @Path("/customers") diff --git a/client/src/main/java/com/cdx/bas/client/bank/transaction/TransactionResource.java b/client/src/main/java/com/cdx/bas/client/bank/transaction/TransactionResource.java index d54bc9af..2ac8d18d 100644 --- a/client/src/main/java/com/cdx/bas/client/bank/transaction/TransactionResource.java +++ b/client/src/main/java/com/cdx/bas/client/bank/transaction/TransactionResource.java @@ -1,5 +1,7 @@ package com.cdx.bas.client.bank.transaction; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; import com.cdx.bas.domain.exception.DomainException; import com.cdx.bas.domain.bank.transaction.*; import com.cdx.bas.domain.message.MessageFormatter; @@ -57,7 +59,11 @@ public Set getAllByStatus(@PathParam("status") String status) { @Produces(MediaType.APPLICATION_JSON) @Override public Transaction findById(@PathParam("id") long id) { - return transactionServicePort.findTransaction(id); + try { + return transactionServicePort.findTransaction(id); + } catch (TransactionException exception) { + throw new WebApplicationException(exception.getMessage(), Response.Status.NOT_FOUND); + } } @POST @@ -134,7 +140,7 @@ public Response deposit(NewCashTransaction newDepositTransaction) { return Response.status(Response.Status.BAD_REQUEST).entity(exception.getMessage()).build(); } catch (Exception exception) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(MessageFormatter.format(TRANSACTION_CONTEXT, DIGITAL_TRANSACTION_ACTION, UNEXPECTED_STATUS)) + .entity(MessageFormatter.format(TRANSACTION_CONTEXT, CASH_TRANSACTION_ACTION, UNEXPECTED_STATUS)) .build(); } } 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 27197e13..4311549a 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 @@ -6,13 +6,17 @@ import com.cdx.bas.domain.bank.account.mma.MMABankAccount; import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import com.cdx.bas.domain.money.Money; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import java.math.BigDecimal; import java.time.Instant; @@ -21,19 +25,20 @@ import java.util.List; import java.util.Set; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.CREDIT; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; @QuarkusTest @WithTestResource(H2DatabaseTestResource.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class BankAccountResourceTest { @Inject BankAccountResource bankAccountResource; @Test - public void getAll_shouldReturnAllBankAccount() { + @Order(1) + void getAll_shouldReturnAllBankAccount() { List expectedCustomers = List.of( new CheckingBankAccount(1L, Money.of(new BigDecimal("400.00")), Set.of(1L), new HashSet<>()), new CheckingBankAccount(2L, Money.of(new BigDecimal("1600.00")), Set.of(2L, 3L), new HashSet<>()), @@ -49,12 +54,12 @@ public void getAll_shouldReturnAllBankAccount() { assertThat(actualTransactions) .usingRecursiveComparison() .ignoringCollectionOrder() - .ignoringFields("issuedTransactions") + .ignoringFields("issuedTransactions", "incomingTransactions") .isEqualTo(expectedCustomers); } @Test - public void findById_shouldReturnBankAccount_whenBankAccountFound() { + void findById_shouldReturnBankAccount_whenBankAccountFound() { BankAccount expectedBankAccount = new SavingBankAccount(); expectedBankAccount.setId(6L); expectedBankAccount.setBalance(Money.of(new BigDecimal("999.00"))); @@ -69,30 +74,29 @@ public void findById_shouldReturnBankAccount_whenBankAccountFound() { } @Test - public void findById_shouldReturnBankAccountWithTransactions_whenTransactionsFoundInBankAccount() { - Transaction transaction1 = Transaction.builder() - .id(3L) - .emitterAccountId(6L) - .receiverAccountId(3L) - .amount(new BigDecimal("9200.00")) - .currency("EUR") - .type(CREDIT) - .status(TransactionStatus.COMPLETED) - .date(Instant.parse("2024-07-10T14:00:00Z")) - .label("transaction 3") - .build(); + void findById_shouldReturnBankAccountWithTransactions_whenTransactionsFoundInBankAccount() { + Transaction transaction1 = new Transaction(); + transaction1.setId(3L); + transaction1.setEmitterAccountId(6L); + transaction1.setReceiverAccountId(3L); + transaction1.setAmount(new BigDecimal("9200.00")); + transaction1.setCurrency("EUR"); + transaction1.setType(TransactionType.CREDIT); // Assurez-vous que TransactionType.CREDIT est défini + transaction1.setStatus(TransactionStatus.COMPLETED); // Assurez-vous que TransactionStatus.COMPLETED est défini + transaction1.setDate(Instant.parse("2024-07-10T14:00:00Z")); + transaction1.setLabel("transaction 3"); + + Transaction transaction2 = new Transaction(); + transaction2.setId(2L); + transaction2.setEmitterAccountId(6L); + transaction2.setReceiverAccountId(3L); + transaction2.setAmount(new BigDecimal("9200.00")); + transaction2.setCurrency("EUR"); + transaction2.setType(TransactionType.CREDIT); + transaction2.setStatus(TransactionStatus.ERROR); // Assurez-vous que TransactionStatus.ERROR est défini + transaction2.setDate(Instant.parse("2024-07-10T14:00:00Z")); + transaction2.setLabel("transaction 2"); - Transaction transaction2 = Transaction.builder() - .id(2L) - .emitterAccountId(6L) - .receiverAccountId(3L) - .amount(new BigDecimal("9200.00")) - .currency("EUR") - .type(CREDIT) - .status(TransactionStatus.ERROR) - .date(Instant.parse("2024-07-10T14:00:00Z")) - .label("transaction 2") - .build(); List issuedTransaction = new ArrayList<>(); issuedTransaction.add(transaction1); issuedTransaction.add(transaction2); @@ -108,7 +112,7 @@ public void findById_shouldReturnBankAccountWithTransactions_whenTransactionsFou } @Test - public void findById_shouldReturnEmptyTransaction_whenTransactionNotFound() { + void findById_shouldReturnEmptyTransaction_whenTransactionNotFound() { try { bankAccountResource.findById(99L); fail(); diff --git a/client/src/test/java/com/cdx/bas/client/bank/customer/CustomerResourceTest.java b/client/src/test/java/com/cdx/bas/client/bank/customer/CustomerResourceTest.java index a4ee2859..c8739738 100644 --- a/client/src/test/java/com/cdx/bas/client/bank/customer/CustomerResourceTest.java +++ b/client/src/test/java/com/cdx/bas/client/bank/customer/CustomerResourceTest.java @@ -25,7 +25,7 @@ class CustomerResourceTest { CustomerResource customerResource; @Test - public void getAll_shouldReturnAllCustomers() { + void getAll_shouldReturnAllCustomers() { Set expectedCustomers = Set.of( new Customer(1L, "John", "Doe", MALE, SINGLE, LocalDate.of(1980, 1, 1), "US", "200 Central Park West, NY 10024", "New York", "johndoe@bas.com", "+1 212-769-5100", Collections.emptyList(), Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "false")), new Customer(2L, "Anne", "Jean", FEMALE, MARRIED, LocalDate.of(1993, 7, 11), "FR", "2 rue du chateau", "Marseille", "annej@bas.com", "+36 6 50 44 12 05", Collections.emptyList(), Map.of("contact_preferences", "phone", "annual_salary", "52000", "newsletter", "false")), @@ -44,7 +44,7 @@ public void getAll_shouldReturnAllCustomers() { } @Test - public void getCustomer_shouldReturnCustomer() { + void getCustomer_shouldReturnCustomer() { Customer expectedCustomer = new Customer(6L, "Juan", "Pedros", MALE, SINGLE, LocalDate.of(1975, 12, 17), "ES", "Place de las Delicias", "Sevilla", "juanito@bas.com", "+34 9 20 55 62 05", Collections.emptyList(), Map.of("contact_preferences", "phone", "annual_salary", "200000", "newsletter", "false")); Customer actualCustomer = customerResource.getCustomer(6L); diff --git a/client/src/test/java/com/cdx/bas/client/bank/transaction/TransactionResourceTest.java b/client/src/test/java/com/cdx/bas/client/bank/transaction/TransactionResourceTest.java index 17066833..4e4b0b78 100644 --- a/client/src/test/java/com/cdx/bas/client/bank/transaction/TransactionResourceTest.java +++ b/client/src/test/java/com/cdx/bas/client/bank/transaction/TransactionResourceTest.java @@ -1,11 +1,13 @@ package com.cdx.bas.client.bank.transaction; -import com.cdx.bas.domain.bank.transaction.NewCashTransaction; +import com.cdx.bas.application.bank.transaction.TransactionRepository; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; import com.cdx.bas.domain.bank.transaction.Transaction; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; import org.junit.jupiter.api.*; @@ -14,7 +16,7 @@ import java.util.*; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.*; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.*; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.*; import static org.assertj.core.api.Assertions.assertThat; @QuarkusTest @@ -22,13 +24,16 @@ @WithTestResource(H2DatabaseTestResource.class) class TransactionResourceTest { + @Inject + TransactionRepository transactionRepository; + @Inject TransactionResource transactionResource; @Test @Order(1) - public void getAll_shouldReturnAllTransactions() { - Set expectedCustomers = Set.of( + void getAll_shouldReturnAllTransactions() { + Set expectedTransactions = Set.of( new Transaction(1L, 1L, 2L, new BigDecimal("1600.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-06-06T11:00:00+00:00"), "transaction 1", Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")), new Transaction(2L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, ERROR, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 2", Map.of("error", "Transaction 2 deposit error for amount 9200 ...")), new Transaction(3L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 3", Map.of("emitter_amount_before", "9200", "receiver_amount_before", "10000", "emitter_amount_after", "0", "receiver_amount_after", "19200")), @@ -37,19 +42,18 @@ public void getAll_shouldReturnAllTransactions() { new Transaction(6L, 1L, 7L, new BigDecimal("2000.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-11-06T17:30:00+00:00"), "transaction 6", new HashMap<>()), new Transaction(7L, 3L, 1L, new BigDecimal("1000.00"), "EUR", CREDIT, UNPROCESSED, Instant.parse("2024-12-06T17:00:00+00:00"), "transaction 7", new HashMap<>()), new Transaction(8L, 4L, 2L, new BigDecimal("300.80"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:00+00:00"), "transaction 8", new HashMap<>()), - new Transaction(9L, 8L, 7L, new BigDecimal("5000.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 9", new HashMap<>()) + new Transaction(9L, 8L, 7L, new BigDecimal("5000.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 9", new HashMap<>()), + new Transaction(10L, 1L, null, new BigDecimal("100.00"), "EUR", DEPOSIT, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 10", new HashMap<>()), + new Transaction(11L, 1L, null, new BigDecimal("200.00"), "EUR", WITHDRAW, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 11", new HashMap<>()) ); Set actualTransactions = transactionResource.getAll(); - assertThat(actualTransactions) - .usingRecursiveComparison() - .ignoringCollectionOrder() - .isEqualTo(expectedCustomers); + assertThat(actualTransactions).containsExactlyInAnyOrderElementsOf(expectedTransactions); } @Test @Order(2) - public void getAllByStatus_shouldReturnEmptySet_whenStatusIsInvalid() { + void getAllByStatus_shouldReturnEmptySet_whenStatusIsInvalid() { Set expectedCustomers = Collections.emptySet(); Set actualTransactions = transactionResource.getAllByStatus(""); @@ -61,10 +65,12 @@ public void getAllByStatus_shouldReturnEmptySet_whenStatusIsInvalid() { @Test @Order(3) - public void getAllByStatus_shouldReturnTransactionWithCompletedStatus() { + void getAllByStatus_shouldReturnTransactionWithCompletedStatus() { Set expectedCustomers = Set.of( new Transaction(1L, 1L, 2L, new BigDecimal("1600.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-06-06T11:00:00+00:00"), "transaction 1", Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")), - new Transaction(3L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 3", Map.of("emitter_amount_before", "9200", "receiver_amount_before", "10000", "emitter_amount_after", "0", "receiver_amount_after", "19200")) + new Transaction(3L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 3", Map.of("emitter_amount_before", "9200", "receiver_amount_before", "10000", "emitter_amount_after", "0", "receiver_amount_after", "19200")), + new Transaction(10L, 1L, null, new BigDecimal("100.00"), "EUR", DEPOSIT, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 10", new HashMap<>()), + new Transaction(11L, 1L, null, new BigDecimal("200.00"), "EUR", WITHDRAW, COMPLETED, Instant.parse("2024-12-06T18:00:10+00:00"), "transaction 11", new HashMap<>()) ); Set actualTransactions = transactionResource.getAllByStatus("completed"); @@ -76,7 +82,7 @@ public void getAllByStatus_shouldReturnTransactionWithCompletedStatus() { @Test @Order(4) - public void getAllByStatus_shouldReturnTransactionWithUnprocessedStatus() { + void getAllByStatus_shouldReturnTransactionWithUnprocessedStatus() { Set expectedCustomers = Set.of( new Transaction(5L, 2L, 1L, new BigDecimal("600.99"), "EUR", CREDIT, UNPROCESSED, Instant.parse("2024-11-06T17:00:00+00:00"), "transaction 5", new HashMap<>()), new Transaction(6L, 1L, 7L, new BigDecimal("2000.00"), "EUR", DEBIT, UNPROCESSED, Instant.parse("2024-11-06T17:30:00+00:00"), "transaction 6", new HashMap<>()), @@ -94,7 +100,7 @@ public void getAllByStatus_shouldReturnTransactionWithUnprocessedStatus() { @Test @Order(5) - public void getAllByStatus_shouldReturnTransactionWithErrorStatus() { + void getAllByStatus_shouldReturnTransactionWithErrorStatus() { Set expectedCustomers = Set.of( new Transaction(2L, 6L, 3L, new BigDecimal("9200.00"), "EUR", CREDIT, ERROR, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 2", Map.of("error", "Transaction 2 deposit error for amount 9200 ...")) ); @@ -108,7 +114,7 @@ public void getAllByStatus_shouldReturnTransactionWithErrorStatus() { @Test @Order(6) - public void getAllByStatus_shouldReturnTransactionWithRefusedStatus() { + void getAllByStatus_shouldReturnTransactionWithRefusedStatus() { Set expectedCustomers = Set.of( new Transaction(4L, 5L, 1L, new BigDecimal("100000.00"), "EUR", CREDIT, REFUSED, Instant.parse("2024-07-10T14:00:00+00:00"), "transaction 4", Map.of("error", "Transaction 4 deposit error for amount 100000 ...")) ); @@ -122,7 +128,7 @@ public void getAllByStatus_shouldReturnTransactionWithRefusedStatus() { @Order(7) @Test - public void findById_shouldReturnTransaction_whenTransactionFound() { + void findById_shouldReturnTransaction_whenTransactionFound() { Transaction expectedTransaction = new Transaction(1L, 1L, 2L, new BigDecimal("1600.00"), "EUR", CREDIT, COMPLETED, Instant.parse("2024-06-06T11:00:00+00:00"), "transaction 1", Map.of("emitter_amount_before", "2000", "receiver_amount_before", "0", "emitter_amount_after", "400", "receiver_amount_after", "1600")); Transaction actualTransaction = transactionResource.findById(1L); @@ -132,23 +138,29 @@ public void findById_shouldReturnTransaction_whenTransactionFound() { @Order(8) @Test - public void findById_shouldReturnEmptyTransaction_whenTransactionNotFound() { - Transaction actualTransaction = transactionResource.findById(99L); - assertThat(actualTransaction).isNull(); + void findById_shouldReturnEmptyTransaction_whenTransactionNotFound() { + long id = 99L; + try { + transactionResource.findById(id); + } catch (WebApplicationException exception) { + assertThat(exception).hasMessage("Transaction: searching failed - not found\n" + + "Transaction id:" + id); + } + } @Test @Order(9) - public void withdraw_shouldReturnAcceptedResponse_whenTransactionIsValidated() { + void withdraw_shouldReturnAcceptedResponse_whenTransactionIsValidated() { Instant timestampBefore = Instant.now(); Map metadata = new HashMap<>(); metadata.put("bill", "500,500"); - NewCashTransaction validNewTransaction = new NewCashTransaction(2L, new BigDecimal("1000.00"), "EUR", metadata); - Transaction expectedCreatedTransaction = new Transaction(10L, 2L, null, new BigDecimal("1000.00"), "EUR", WITHDRAW, COMPLETED, timestampBefore, "withdraw:1000.00 EUR", + NewCashTransaction validNewTransaction = new NewCashTransaction(2L, new BigDecimal("1000.00"), "EUR", metadata); + Transaction expectedCreatedTransaction = new Transaction(20L, 2L, null, new BigDecimal("1000.00"), "EUR", WITHDRAW, COMPLETED, timestampBefore, "withdraw:1000.00 EUR", Map.of("emitter_amount_after", "600.00", "emitter_amount_before", "1600.00")); Response actualResponse = transactionResource.withdraw(validNewTransaction); - Transaction actualTransaction = transactionResource.findById(10L); + Transaction actualTransaction = transactionResource.findById(20L); Instant timestampAfter = Instant.now(); String expectedMessage = "Transaction: withdraw accepted"; assertThat(actualResponse.getEntity()).isEqualTo(expectedMessage); @@ -161,59 +173,58 @@ public void withdraw_shouldReturnAcceptedResponse_whenTransactionIsValidated() { @Test @Order(10) - @Disabled - public void withdraw_shouldReturnErrorResponse_whenTransactionIsInvalid() { - NewCashTransaction invalidNewTransaction = new NewCashTransaction(1L, null, null, null); - List expectedLines = Arrays.asList( - "Amount must not be null.", - "Metadata must not be null.", - "Emitter account id must not be null.", - "Currency must not be null."); - - Response actualResponse = transactionResource.withdraw(invalidNewTransaction); - List actualLines = Arrays.asList(actualResponse.getEntity().toString().split("\n")); - assertThat(actualLines) - .hasSize(4) - .containsExactlyInAnyOrderElementsOf(expectedLines); - } - - @Test - @Order(11) - public void deposit_shouldReturnAcceptedResponse_whenTransactionIsValidated() { + void deposit_shouldReturnAcceptedResponse_whenTransactionIsValidated() { Instant timestampBefore = Instant.now(); Map metadata = new HashMap<>(); metadata.put("bill", "500,500"); - NewCashTransaction validNewTransaction = new NewCashTransaction(1L, new BigDecimal("1000.00"), "EUR", metadata); - Transaction expectedCreatedTransaction = new Transaction(11L, 1L, null, new BigDecimal("1000.00"), "EUR", DEPOSIT, COMPLETED, timestampBefore, "deposit:1000.00 EUR", + NewCashTransaction validNewTransaction = new NewCashTransaction(1L, new BigDecimal("1000.00"), "EUR", metadata); + Transaction expectedCreatedTransaction = new Transaction(21L, 1L, null, new BigDecimal("1000.00"), "EUR", DEPOSIT, COMPLETED, timestampBefore, "deposit:1000.00 EUR", Map.of("emitter_amount_before", "400.00", "emitter_amount_after", "1400.00")); - Response actualResponse = transactionResource.deposit(validNewTransaction); - Transaction actualTransaction = transactionResource.findById(11L); - Instant timestampAfter = Instant.now(); + Response actualResponse = transactionResource.deposit(validNewTransaction); + Transaction actualTransaction = transactionResource.findById(21L); + Instant timestampAfter = Instant.now(); String expectedMessage = "Transaction: deposit accepted"; assertThat(actualResponse.getEntity()).isEqualTo(expectedMessage); - assertThat(actualTransaction) - .usingRecursiveComparison() - .ignoringFields("date") - .isEqualTo(expectedCreatedTransaction); - assertThat(actualTransaction.getDate()).isAfter(timestampBefore).isBefore(timestampAfter); + assertThat(actualTransaction) + .usingRecursiveComparison() + .ignoringFields("date") + .isEqualTo(expectedCreatedTransaction); + assertThat(actualTransaction.getDate()).isAfter(timestampBefore).isBefore(timestampAfter); + } + + + @Test + @Order(11) + void withdraw_shouldReturnErrorResponse_whenTransactionIsInvalid() { + NewCashTransaction invalidNewTransaction = new NewCashTransaction(1L, null, null, null); + List expectedLines = Arrays.asList("Withdraw transaction: withdraw refused - domain error", + "Transaction id:null", + "Error:Amount must not be null.", + "Bill must be define for cash movements.", + "Currency must not be null."); + + Response actualResponse = transactionResource.withdraw(invalidNewTransaction); + List actualLines = Arrays.asList(actualResponse.getEntity().toString().split("\n")); + assertThat(actualLines) + .containsExactlyInAnyOrderElementsOf(expectedLines); } @Test @Order(12) - @Disabled - public void deposit_shouldReturnErrorResponse_whenTransactionIsInvalid() { + void deposit_shouldReturnErrorResponse_whenTransactionIsInvalid() { NewCashTransaction invalidNewTransaction = new NewCashTransaction(null, null, null, null); List expectedLines = Arrays.asList( - "Amount must not be null.", - "Metadata must not be null.", + "Deposit transaction: deposit refused - domain error", + "Transaction id:null", + "Error:Amount must not be null.", + "Bill must be define for cash movements.", "Emitter account id must not be null.", "Currency must not be null."); Response actualResponse = transactionResource.deposit(invalidNewTransaction); List actualLines = Arrays.asList(actualResponse.getEntity().toString().split("\n")); assertThat(actualLines) - .hasSize(4) .containsExactlyInAnyOrderElementsOf(expectedLines); } } \ No newline at end of file diff --git a/domain/pom.xml b/domain/pom.xml index f53df0c3..2b2f23ff 100644 --- a/domain/pom.xml +++ b/domain/pom.xml @@ -47,11 +47,6 @@ io.quarkus quarkus-hibernate-validator - - org.projectlombok - lombok - provided - org.apache.commons commons-lang3 @@ -80,6 +75,14 @@ - + + org.apache.maven.plugins + maven-compiler-plugin + + 21 + 21 + + + diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccount.java b/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccount.java index 11785c45..d38f0aba 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccount.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccount.java @@ -1,20 +1,17 @@ package com.cdx.bas.domain.bank.account; import com.cdx.bas.domain.bank.account.type.AccountType; -import com.cdx.bas.domain.money.Money; import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.money.Money; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import lombok.*; -import lombok.experimental.SuperBuilder; - -import java.util.*; -@Data -@SuperBuilder -@AllArgsConstructor -@NoArgsConstructor + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + public abstract class BankAccount { @NotNull(message="id must not be null.") @@ -34,8 +31,83 @@ public abstract class BankAccount { @NotNull(message="issued transactions must not be null.") protected Set issuedTransactions = new HashSet<>(); + + @NotNull(message="issued transactions must not be null.") + protected Set incomingTransactions = new HashSet<>(); protected BankAccount(AccountType type) { this.type = type; } + + protected BankAccount() { + } + + protected BankAccount(Long id, AccountType type, Money balance, Set customersId, Set issuedTransactions) { + this.id = id; + this.type = type; + this.balance = balance; + this.customersId = customersId; + this.issuedTransactions = issuedTransactions; + } + + public @NotNull(message = "id must not be null.") @Min(value = 1, message = "id must be positive and greater than 0.") Long getId() { + return id; + } + + public void setId(@NotNull(message = "id must not be null.") @Min(value = 1, message = "id must be positive and greater than 0.") Long id) { + this.id = id; + } + + public @NotNull(message = "type must not be null.") AccountType getType() { + return type; + } + + public void setType(@NotNull(message = "type must not be null.") AccountType type) { + this.type = type; + } + + public @NotNull(message = "balance must not be null.") @Valid Money getBalance() { + return balance; + } + + public void setBalance(@NotNull(message = "balance must not be null.") @Valid Money balance) { + this.balance = balance; + } + + public @NotNull(message = "customersId must not be null.") @Size(min = 1, message = "customersId must contains at least 1 customer id.") Set getCustomersId() { + return customersId; + } + + public void setCustomersId(@NotNull(message = "customersId must not be null.") @Size(min = 1, message = "customersId must contains at least 1 customer id.") Set customersId) { + this.customersId = customersId; + } + + public @NotNull(message = "issued transactions must not be null.") Set getIssuedTransactions() { + return issuedTransactions; + } + + public void setIssuedTransactions(@NotNull(message = "issued transactions must not be null.") Set issuedTransactions) { + this.issuedTransactions = issuedTransactions; + } + + public @NotNull(message = "issued transactions must not be null.") Set getIncomingTransactions() { + return incomingTransactions; + } + + public void setIncomingTransactions(@NotNull(message = "issued transactions must not be null.") Set incomingTransactions) { + this.incomingTransactions = incomingTransactions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BankAccount that = (BankAccount) o; + return Objects.equals(id, that.id) && type == that.type && Objects.equals(balance, that.balance) && Objects.equals(customersId, that.customersId) && Objects.equals(issuedTransactions, that.issuedTransactions); + } + + @Override + public int hashCode() { + return Objects.hash(id, type, balance, customersId, issuedTransactions); + } } 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 6ae5c49f..85e1dde4 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 @@ -30,15 +30,6 @@ public interface BankAccountServicePort { */ BankAccount putTransaction(Transaction transaction, BankAccount bankAccount); - /** - * add transaction to bank account - * - * @param transaction to post with amount and currency - * @param emitterBankAccount which emits transaction - * @param receiverBankAccount which receives transaction - */ - void creditAmountToAccounts(Transaction transaction, BankAccount emitterBankAccount, BankAccount receiverBankAccount) ; - /** * updated bank account * @@ -46,8 +37,4 @@ public interface BankAccountServicePort { * @return bank account updated */ BankAccount updateBankAccount(BankAccount bankAccount); - - void depositAmountToAccount(Transaction transaction, BankAccount emitterBankAccount); - - void withdrawAmountToAccount(Transaction transaction, BankAccount emitterBankAccount); } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/account/checking/CheckingBankAccount.java b/domain/src/main/java/com/cdx/bas/domain/bank/account/checking/CheckingBankAccount.java index 8fd1a02d..57fa7225 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/account/checking/CheckingBankAccount.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/account/checking/CheckingBankAccount.java @@ -1,37 +1,32 @@ package com.cdx.bas.domain.bank.account.checking; import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.type.AccountType; import com.cdx.bas.domain.bank.transaction.Transaction; import com.cdx.bas.domain.money.Amount; import com.cdx.bas.domain.money.Money; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.SuperBuilder; - -import java.util.List; +import com.cdx.bas.domain.testing.Generated; + import java.util.Set; -import static com.cdx.bas.domain.bank.account.type.AccountType.*; +import static com.cdx.bas.domain.bank.account.type.AccountType.CHECKING; /** * Checking account (transaction account/current account) */ -@SuperBuilder public class CheckingBankAccount extends BankAccount { + @Override @Amount(min=-600, max=100000, message="balance amount must be between -600 and 100000.") public Money getBalance() { return super.balance; } - + + @Generated public CheckingBankAccount() { super(CHECKING); } + @Generated public CheckingBankAccount(Long id, Money balance, Set customersId, Set issuedTransactions) { super(id, CHECKING, balance, customersId, issuedTransactions); } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/account/mma/MMABankAccount.java b/domain/src/main/java/com/cdx/bas/domain/bank/account/mma/MMABankAccount.java index a775a89b..ceea7223 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/account/mma/MMABankAccount.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/account/mma/MMABankAccount.java @@ -1,37 +1,32 @@ package com.cdx.bas.domain.bank.account.mma; import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.type.AccountType; import com.cdx.bas.domain.bank.transaction.Transaction; import com.cdx.bas.domain.money.Amount; import com.cdx.bas.domain.money.Money; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.SuperBuilder; +import com.cdx.bas.domain.testing.Generated; -import java.util.List; import java.util.Set; -import static com.cdx.bas.domain.bank.account.type.AccountType.*; +import static com.cdx.bas.domain.bank.account.type.AccountType.MMA; /** * Money Market Account */ -@SuperBuilder public class MMABankAccount extends BankAccount { + @Override @Amount(min=1000, max=250000, message="balance amount must be between 1000 and 250000.") public Money getBalance() { return super.balance; } - + + @Generated public MMABankAccount() { super(MMA); } + @Generated public MMABankAccount(Long id, Money balance, Set customersId, Set issuedTransactions) { super(id, MMA, balance, customersId, issuedTransactions); } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/account/saving/SavingBankAccount.java b/domain/src/main/java/com/cdx/bas/domain/bank/account/saving/SavingBankAccount.java index b1021073..14dfa40b 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/account/saving/SavingBankAccount.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/account/saving/SavingBankAccount.java @@ -1,18 +1,11 @@ package com.cdx.bas.domain.bank.account.saving; import com.cdx.bas.domain.bank.account.BankAccount; -import com.cdx.bas.domain.bank.account.type.AccountType; import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; import com.cdx.bas.domain.money.Amount; import com.cdx.bas.domain.money.Money; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import lombok.experimental.SuperBuilder; +import com.cdx.bas.domain.testing.Generated; -import java.math.BigDecimal; -import java.util.List; import java.util.Set; import static com.cdx.bas.domain.bank.account.type.AccountType.*; @@ -20,18 +13,20 @@ /** * Saving Account (French Livret A) */ -@SuperBuilder public class SavingBankAccount extends BankAccount { + @Override @Amount(min=1, max=22950, message="balance amount must be between 1 and 22950.") - public Money getBalance() { + public Money getBalance() { return super.balance; } - + + @Generated public SavingBankAccount() { super(SAVING); } + @Generated public SavingBankAccount(Long id, Money balance, Set customersId, Set issuedTransactions) { super(id, SAVING, balance, customersId, issuedTransactions); } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/customer/Customer.java b/domain/src/main/java/com/cdx/bas/domain/bank/customer/Customer.java index 7ac550e0..a256ddeb 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/customer/Customer.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/customer/Customer.java @@ -62,15 +62,11 @@ public class Customer { private List accounts = new ArrayList<>(); private Map metadata = new HashMap<>(); - + public Customer() { - super(); } - public Customer(Long id, String firstName, String lastName, Gender gender, MaritalStatus maritalStatus, - LocalDate birthdate, String country, String address, String city, String email, String phoneNumber, - List accounts, Map metadata) { - super(); + public Customer(Long id, String firstName, String lastName, Gender gender, MaritalStatus maritalStatus, LocalDate birthdate, String country, String address, String city, String email, String phoneNumber, List accounts, Map metadata) { this.id = id; this.firstName = firstName; this.lastName = lastName; @@ -86,102 +82,102 @@ public Customer(Long id, String firstName, String lastName, Gender gender, Marit this.metadata = metadata; } - public Long getId() { + public @NotNull(message = "id must not be null.") @Min(value = 1, message = "id must be positive and greater than 0.") Long getId() { return id; } - public void setId(Long id) { + public void setId(@NotNull(message = "id must not be null.") @Min(value = 1, message = "id must be positive and greater than 0.") Long id) { this.id = id; } - public String getFirstName() { + public @NotNull(message = "firstName must not be null.") @Size(min = 1, max = 750, message = "firstName must contain at least 1 character and must not have more than 750 characters.") String getFirstName() { return firstName; } - - public void setFirstName(String firstName) { + + public void setFirstName(@NotNull(message = "firstName must not be null.") @Size(min = 1, max = 750, message = "firstName must contain at least 1 character and must not have more than 750 characters.") String firstName) { this.firstName = firstName; } - - public String getLastName() { + + public @NotNull(message = "lastName must not be null.") @Size(min = 1, max = 750, message = "lastName must contain at least 1 character and must not have more than 750 characters.") String getLastName() { return lastName; } - - public void setLastName(String lastName) { + + public void setLastName(@NotNull(message = "lastName must not be null.") @Size(min = 1, max = 750, message = "lastName must contain at least 1 character and must not have more than 750 characters.") String lastName) { this.lastName = lastName; } - - public Gender getGender() { + + public @NotNull(message = "gender must not be null.") Gender getGender() { return gender; } - - public void setGender(Gender gender) { + + public void setGender(@NotNull(message = "gender must not be null.") Gender gender) { this.gender = gender; } - - public MaritalStatus getMaritalStatus() { + + public @NotNull(message = "maritalStatus must not be null.") MaritalStatus getMaritalStatus() { return maritalStatus; } - - public void setMaritalStatus(MaritalStatus maritalStatus) { + + public void setMaritalStatus(@NotNull(message = "maritalStatus must not be null.") MaritalStatus maritalStatus) { this.maritalStatus = maritalStatus; } - public LocalDate getBirthdate() { + public @NotNull(message = "birthdate must not be null.") @Past(message = "birthdate must not be before the current date.") LocalDate getBirthdate() { return birthdate; } - public void setBirthdate(LocalDate birthdate) { + public void setBirthdate(@NotNull(message = "birthdate must not be null.") @Past(message = "birthdate must not be before the current date.") LocalDate birthdate) { this.birthdate = birthdate; } - public String getCountry() { + public @NotNull(message = "country must not be null.") @Pattern(regexp = ISO_COUNTRY_REGEX, message = "country must contain ISO 3166 country code.") String getCountry() { return country; } - - public void setCountry(String country) { + + public void setCountry(@NotNull(message = "country must not be null.") @Pattern(regexp = ISO_COUNTRY_REGEX, message = "country must contain ISO 3166 country code.") String country) { this.country = country; } - - public String getAddress() { + + public @NotNull(message = "address must not be null.") @Size(min = 1, message = "address must contain at least 1 character.") String getAddress() { return address; } - - public void setAddress(String address) { + + public void setAddress(@NotNull(message = "address must not be null.") @Size(min = 1, message = "address must contain at least 1 character.") String address) { this.address = address; } - - public String getCity() { + + public @NotNull(message = "city must not be null.") @Size(min = 1, message = "city must contain at least 1 character.") String getCity() { return city; } - - public void setCity(String city) { + + public void setCity(@NotNull(message = "city must not be null.") @Size(min = 1, message = "city must contain at least 1 character.") String city) { this.city = city; } - - public String getEmail() { + + public @NotNull(message = "email must not be null.") @Email(message = "email must respect the email format.") @Size(min = 1, message = "address must contain at least 1 character.") String getEmail() { return email; } - - public void setEmail(String email) { + + public void setEmail(@NotNull(message = "email must not be null.") @Email(message = "email must respect the email format.") @Size(min = 1, message = "address must contain at least 1 character.") String email) { this.email = email; } - - public String getPhoneNumber() { + + public @NotNull(message = "phoneNumber must not be null.") @Size(min = 5, max = 20, message = "phoneNumber must contain at least 5 digits and maximum 20 digits.") String getPhoneNumber() { return phoneNumber; } - - public void setPhoneNumber(String phoneNumber) { + + public void setPhoneNumber(@NotNull(message = "phoneNumber must not be null.") @Size(min = 5, max = 20, message = "phoneNumber must contain at least 5 digits and maximum 20 digits.") String phoneNumber) { this.phoneNumber = phoneNumber; } - + public List getAccounts() { return accounts; } - - public void setAccounts(List accounts) { + + public void setAccounts(@NotNull(message = "accounts must not be null.") List accounts) { this.accounts = accounts; } - + public Map getMetadata() { return metadata; } @@ -190,29 +186,16 @@ public void setMetadata(Map metadata) { this.metadata = metadata; } - @Override - public int hashCode() { - return Objects.hash(id, firstName, lastName, gender, maritalStatus, - birthdate, country, address, city, email, phoneNumber, accounts, metadata); - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; - return Objects.equals(id, customer.id) && - Objects.equals(firstName, customer.firstName) && - Objects.equals(lastName, customer.lastName) && - gender == customer.gender && - maritalStatus == customer.maritalStatus && - Objects.equals(birthdate, customer.birthdate) && - Objects.equals(country, customer.country) && - Objects.equals(address, customer.address) && - Objects.equals(city, customer.city) && - Objects.equals(email, customer.email) && - Objects.equals(phoneNumber, customer.phoneNumber) && - Objects.equals(accounts, customer.accounts) && - Objects.equals(metadata, customer.metadata); + return Objects.equals(id, customer.id) && Objects.equals(firstName, customer.firstName) && Objects.equals(lastName, customer.lastName) && gender == customer.gender && maritalStatus == customer.maritalStatus && Objects.equals(birthdate, customer.birthdate) && Objects.equals(country, customer.country) && Objects.equals(address, customer.address) && Objects.equals(city, customer.city) && Objects.equals(email, customer.email) && Objects.equals(phoneNumber, customer.phoneNumber) && Objects.equals(accounts, customer.accounts) && Objects.equals(metadata, customer.metadata); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName, gender, maritalStatus, birthdate, country, address, city, email, phoneNumber, accounts, metadata); } } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerException.java b/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerException.java index 00e592e5..67d74ddb 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerException.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerException.java @@ -1,7 +1,9 @@ package com.cdx.bas.domain.bank.customer; import com.cdx.bas.domain.exception.DomainException; +import com.cdx.bas.domain.testing.Generated; +@Generated public class CustomerException extends DomainException { public CustomerException(String errorMessage) { super(errorMessage); diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerPersistencePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerPersistencePort.java index 5e639e6d..a6fab837 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerPersistencePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/customer/CustomerPersistencePort.java @@ -11,6 +11,13 @@ public interface CustomerPersistencePort { */ public Set getAll(); + /** + * find every Customers from id collection + * + * @param customersId + * @return all Customer + */ + public Set findAllById(Set customersId); /** * find Customer from its id diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/customer/gender/Gender.java b/domain/src/main/java/com/cdx/bas/domain/bank/customer/gender/Gender.java index f8dedca2..a36bb292 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/customer/gender/Gender.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/customer/gender/Gender.java @@ -5,12 +5,12 @@ public enum Gender { FEMALE('F'), OTHER('O'); - public final static char strMale = 'M'; - public final static char strFemale = 'F'; - public final static char strOther = 'O'; + public static final char STR_MALE = 'M'; + public static final char STR_FEMALE = 'F'; + public static final char STR_OTHER = 'O'; private final char genderCode; - Gender(Character maritalCode) { + Gender(Character maritalCode) { this.genderCode = maritalCode; } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/customer/maritalstatus/MaritalStatus.java b/domain/src/main/java/com/cdx/bas/domain/bank/customer/maritalstatus/MaritalStatus.java index cee2f68c..e2c54f54 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/customer/maritalstatus/MaritalStatus.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/customer/maritalstatus/MaritalStatus.java @@ -7,11 +7,11 @@ public enum MaritalStatus { DIVORCED('D'), PACS('P'); - public final static char strSingle = 'S'; - public final static char strMarried = 'M'; - public final static char strWidowed = 'W'; - public final static char strDivorced= 'D'; - public final static char strPacs = 'P'; + public static final char STR_SINGLE = 'S'; + public static final char STR_MARRIED = 'M'; + public static final char STR_WIDOWED = 'W'; + public static final char STR_DIVORCED = 'D'; + public static final char STR_PACS = 'P'; private final char maritalCode; diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/Transaction.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/Transaction.java index 953e71f1..a2e3f2a5 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/Transaction.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/Transaction.java @@ -1,16 +1,12 @@ package com.cdx.bas.domain.bank.transaction; -import com.cdx.bas.domain.bank.transaction.validation.validator.ValidStatus; -import com.cdx.bas.domain.bank.transaction.validation.validator.ValidType; -import com.cdx.bas.domain.bank.transaction.validation.group.*; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.category.digital.type.ValidType; +import com.cdx.bas.domain.bank.transaction.category.group.*; import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.ValidStatus; import com.cdx.bas.domain.currency.validation.ValidCurrency; import jakarta.validation.constraints.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.Instant; @@ -18,13 +14,9 @@ import java.util.Map; import java.util.Objects; +import static com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType.*; import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.*; -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor public class Transaction implements Comparable { @Min(value = 1, message = "Id must be positive and greater than 0 for existing transaction.", groups = ExistingTransactionGroup.class) @@ -75,25 +67,120 @@ public int compareTo(Transaction transactionToCompare) { return this.getDate().compareTo(transactionToCompare.getDate()); } + public Transaction() { + } + + public Transaction(Long id, Long emitterAccountId, Long receiverAccountId, BigDecimal amount, String currency, TransactionType type, TransactionStatus status, Instant date, String label, Map metadata) { + this.id = id; + this.emitterAccountId = emitterAccountId; + this.receiverAccountId = receiverAccountId; + this.amount = amount; + this.currency = currency; + this.type = type; + this.status = status; + this.date = date; + this.label = label; + this.metadata = metadata; + } + + public @Min(value = 1, message = "Id must be positive and greater than 0 for existing transaction.", groups = ExistingTransactionGroup.class) @NotNull(message = "Id must not be null for existing transaction.", groups = ExistingTransactionGroup.class) @Null(message = "Id must be null for new transaction.", groups = NewTransactionGroup.class) @Positive(message = "Id must be positive.", groups = AdvancedGroup.class) Long getId() { + return id; + } + + public void setId(@Min(value = 1, message = "Id must be positive and greater than 0 for existing transaction.", groups = ExistingTransactionGroup.class) @NotNull(message = "Id must not be null for existing transaction.", groups = ExistingTransactionGroup.class) @Null(message = "Id must be null for new transaction.", groups = NewTransactionGroup.class) @Positive(message = "Id must be positive.", groups = AdvancedGroup.class) Long id) { + this.id = id; + } + + public @Positive(message = "Emitter account id must be positive.", groups = AdvancedGroup.class) @NotNull(message = "Emitter account id must not be null.") Long getEmitterAccountId() { + return emitterAccountId; + } + + public void setEmitterAccountId(@Positive(message = "Emitter account id must be positive.", groups = AdvancedGroup.class) @NotNull(message = "Emitter account id must not be null.") Long emitterAccountId) { + this.emitterAccountId = emitterAccountId; + } + + public @Null(message = "Receiver account id must be null for cash movement.", groups = PhysicalCashTransactionGroup.class) @Positive(message = "Receiver account id must be positive.", groups = AdvancedGroup.class) @NotNull(message = "Receiver account id must not be null.", groups = DigitalTransactionGroup.class) Long getReceiverAccountId() { + return receiverAccountId; + } + + public void setReceiverAccountId(@Null(message = "Receiver account id must be null for cash movement.", groups = PhysicalCashTransactionGroup.class) @Positive(message = "Receiver account id must be positive.", groups = AdvancedGroup.class) @NotNull(message = "Receiver account id must not be null.", groups = DigitalTransactionGroup.class) Long receiverAccountId) { + this.receiverAccountId = receiverAccountId; + } + + public @Min(value = 10, message = "Amount must be greater than 10 for cash movement.", groups = PhysicalCashTransactionGroup.class) @Min(value = 1, message = "Amount must be positive and greater than 0.", groups = DigitalTransactionGroup.class) @NotNull(message = "Amount must not be null.") BigDecimal getAmount() { + return amount; + } + + public void setAmount(@Min(value = 10, message = "Amount must be greater than 10 for cash movement.", groups = PhysicalCashTransactionGroup.class) @Min(value = 1, message = "Amount must be positive and greater than 0.", groups = DigitalTransactionGroup.class) @NotNull(message = "Amount must not be null.") BigDecimal amount) { + this.amount = amount; + } + + public @NotNull(message = "Currency must not be null.") String getCurrency() { + return currency; + } + + public void setCurrency(@NotNull(message = "Currency must not be null.") String currency) { + this.currency = currency; + } + + public @NotNull(message = "Type must not be null.") TransactionType getType() { + return type; + } + + public void setType(@NotNull(message = "Type must not be null.") TransactionType type) { + this.type = type; + } + + public @NotNull(message = "Status must not be null.") TransactionStatus getStatus() { + return status; + } + + public void setStatus(@NotNull(message = "Status must not be null.") TransactionStatus status) { + this.status = status; + } + + public @NotNull(message = "Date must not be null.") Instant getDate() { + return date; + } + + public void setDate(@NotNull(message = "Date must not be null.") Instant date) { + this.date = date; + } + + public @NotNull(message = "Label must not be null.") String getLabel() { + return label; + } + + public void setLabel(@NotNull(message = "Label must not be null.") String label) { + this.label = label; + } + + public @NotEmpty(message = "Bill must be define for cash movements.", groups = PhysicalCashTransactionGroup.class) @NotNull(message = "Metadata must not be null.") Map getMetadata() { + return metadata; + } + + public void setMetadata(@NotEmpty(message = "Bill must be define for cash movements.", groups = PhysicalCashTransactionGroup.class) @NotNull(message = "Metadata must not be null.") Map metadata) { + this.metadata = metadata; + } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Transaction that = (Transaction) o; - return Objects.equals(id, that.id) && - Objects.equals(emitterAccountId, that.emitterAccountId) && - Objects.equals(receiverAccountId, that.receiverAccountId) && - Objects.equals(amount, that.amount) && - Objects.equals(currency, that.currency) && - Objects.equals(type, that.type) && - Objects.equals(date, that.date) && - Objects.equals(label, that.label); + return Objects.equals(id, that.id) + && Objects.equals(emitterAccountId, that.emitterAccountId) + && Objects.equals(receiverAccountId, that.receiverAccountId) + && Objects.equals(amount, that.amount) + && Objects.equals(currency, that.currency) + && type == that.type && status == that.status + && Objects.equals(date, that.date) + && Objects.equals(label, that.label) + && Objects.equals(metadata, that.metadata); } - // Redéfinir hashCode() en excluant status et metadata @Override public int hashCode() { - return Objects.hash(id, emitterAccountId, receiverAccountId, amount, currency, type, date, label); + return Objects.hash(id, emitterAccountId, receiverAccountId, amount, currency, type, status, date, label, metadata); } } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionControllerPort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionControllerPort.java index 461324fa..7d28044b 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionControllerPort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionControllerPort.java @@ -1,14 +1,10 @@ package com.cdx.bas.domain.bank.transaction; -import jakarta.transaction.Transactional; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import java.math.BigDecimal; import java.util.Set; public interface TransactionControllerPort { diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionPersistencePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionPersistencePort.java index a9d6a57f..d1116892 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionPersistencePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionPersistencePort.java @@ -16,6 +16,22 @@ public interface TransactionPersistencePort { */ public Optional findById(long id); + /** + * find Transaction from its id + * + * @param emitterBankAccountId of Transaction + * @return Set Transactions that match with emitter id + */ + public Set findTransactionsByEmitterBankAccount(long emitterBankAccountId); + + /** + * find Transaction from its id + * + * @param receiverBankAccountId of Transaction + * @return Set Transactions that match with receiver id + */ + public Set findTransactionsByReceiverBankAccount(long receiverBankAccountId); + /** * find all transactions * diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionServicePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionServicePort.java index d455f9e3..31c5be3d 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionServicePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionServicePort.java @@ -1,8 +1,8 @@ package com.cdx.bas.domain.bank.transaction; -import jakarta.transaction.Transactional; +import com.cdx.bas.domain.bank.transaction.category.NewCashTransaction; +import com.cdx.bas.domain.bank.transaction.category.NewDigitalTransaction; -import java.util.Map; import java.util.Set; public interface TransactionServicePort { @@ -18,17 +18,15 @@ public interface TransactionServicePort { * Create new transaction * * @param transaction to create - * @param metadata with detail about the transaction */ - void create(Transaction transaction, Map metadata); + void create(Transaction transaction); /** * Update existing transaction * * @param transaction to update - * @param metadata with detail about the transaction */ - void update(Transaction transaction, Map metadata); + void update(Transaction transaction); /** @@ -56,7 +54,7 @@ public interface TransactionServicePort { /** * Process digital transaction - * + * * @param digitalTransaction to process */ void processDigitalTransaction(Transaction digitalTransaction); diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/TransactionValidator.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionValidator.java similarity index 87% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/TransactionValidator.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionValidator.java index 3b99ae68..2051b01a 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/TransactionValidator.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/TransactionValidator.java @@ -1,25 +1,24 @@ -package com.cdx.bas.domain.bank.transaction.validation.validator; +package com.cdx.bas.domain.bank.transaction; -import com.cdx.bas.domain.bank.transaction.Transaction; -import com.cdx.bas.domain.bank.transaction.TransactionException; -import com.cdx.bas.domain.bank.transaction.validation.group.*; +import com.cdx.bas.domain.bank.transaction.category.group.*; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; -import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; @RequestScoped public class TransactionValidator { - @Inject Validator validator; + @Inject + public TransactionValidator(Validator validator) { + this.validator = validator; + } + public void validateNewDigitalTransaction(Transaction transaction) throws TransactionException { validateTransaction(transaction, NewTransactionGroup.class, DigitalTransactionGroup.class); } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/NewCashTransaction.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/NewCashTransaction.java similarity index 71% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/NewCashTransaction.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/NewCashTransaction.java index 5ba39993..7dc6e55d 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/NewCashTransaction.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/NewCashTransaction.java @@ -1,6 +1,4 @@ -package com.cdx.bas.domain.bank.transaction; - -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +package com.cdx.bas.domain.bank.transaction.category; import java.math.BigDecimal; import java.util.Map; diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/NewDigitalTransaction.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/NewDigitalTransaction.java similarity index 72% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/NewDigitalTransaction.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/NewDigitalTransaction.java index 8773a06f..ba584460 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/NewDigitalTransaction.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/NewDigitalTransaction.java @@ -1,6 +1,6 @@ -package com.cdx.bas.domain.bank.transaction; +package com.cdx.bas.domain.bank.transaction.category; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; import java.math.BigDecimal; import java.util.Map; diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashAmountServicePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashAmountServicePort.java new file mode 100644 index 00000000..8aff308b --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashAmountServicePort.java @@ -0,0 +1,5 @@ +package com.cdx.bas.domain.bank.transaction.category.cash; + +public interface CashAmountServicePort { + void applyToAccount (CashTransactionProcessingDetails cashTransactionProcessingDetails); +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashTransactionProcessingDetails.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashTransactionProcessingDetails.java new file mode 100644 index 00000000..7823e860 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/CashTransactionProcessingDetails.java @@ -0,0 +1,43 @@ +package com.cdx.bas.domain.bank.transaction.category.cash; + +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.transaction.Transaction; + +import java.util.Map; + + +public class CashTransactionProcessingDetails { + Transaction transaction; + BankAccount emitterBankAccount; + Map metadata; + + public CashTransactionProcessingDetails(Transaction transaction, BankAccount emitterBankAccount, Map metadata) { + this.transaction = transaction; + this.emitterBankAccount = emitterBankAccount; + this.metadata = metadata; + } + + public Transaction getTransaction() { + return transaction; + } + + public void setTransaction(Transaction transaction) { + this.transaction = transaction; + } + + public BankAccount getEmitterBankAccount() { + return emitterBankAccount; + } + + public void setEmitterBankAccount(BankAccount emitterBankAccount) { + this.emitterBankAccount = emitterBankAccount; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImpl.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImpl.java new file mode 100644 index 00000000..edf91c61 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImpl.java @@ -0,0 +1,33 @@ +package com.cdx.bas.domain.bank.transaction.category.cash.type.deposit; + +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.cash.CashAmountServicePort; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.currency.rate.ExchangeRateUtils; +import com.cdx.bas.domain.message.MessageFormatter; +import com.cdx.bas.domain.money.Money; +import jakarta.enterprise.context.ApplicationScoped; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.money.AmountUtils.isNotPositive; + + +@ApplicationScoped +public class DepositAmountServiceImpl implements CashAmountServicePort { + + @Override + public void applyToAccount(CashTransactionProcessingDetails cashTransactionProcessingDetails) { + BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(cashTransactionProcessingDetails.getTransaction().getCurrency(), + cashTransactionProcessingDetails.getTransaction().getAmount()); + if (isNotPositive(euroAmount)) { + throw new TransactionException(MessageFormatter.format(DEPOSIT_TRANSACTION_CONTEXT, DEPOSIT_ACTION, FAILED_STATUS, + Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), + List.of(TRANSACTION_ID_DETAIL + cashTransactionProcessingDetails.getTransaction().getId(), EURO_AMOUNT_DETAIL + euroAmount))); + } + cashTransactionProcessingDetails.getEmitterBankAccount().getBalance().plus(Money.of(euroAmount)); + } +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImpl.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImpl.java new file mode 100644 index 00000000..7ba3acb3 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImpl.java @@ -0,0 +1,31 @@ +package com.cdx.bas.domain.bank.transaction.category.cash.type.withdraw; + +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.cash.CashAmountServicePort; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.currency.rate.ExchangeRateUtils; +import com.cdx.bas.domain.money.Money; +import jakarta.enterprise.context.ApplicationScoped; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.message.MessageFormatter.format; +import static com.cdx.bas.domain.money.AmountUtils.isNotPositive; + +@ApplicationScoped +public class WithdrawAmountServiceImpl implements CashAmountServicePort { + @Override + public void applyToAccount(CashTransactionProcessingDetails cashTransactionProcessingDetails) { + BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(cashTransactionProcessingDetails.getTransaction().getCurrency(), + cashTransactionProcessingDetails.getTransaction().getAmount()); + if (isNotPositive(euroAmount)) { + throw new TransactionException(format(WITHDRAW_TRANSACTION_CONTEXT, WITHDRAW_ACTION, FAILED_STATUS, + Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), + List.of(TRANSACTION_ID_DETAIL + cashTransactionProcessingDetails.getTransaction().getId(), EURO_AMOUNT_DETAIL + euroAmount))); + } + cashTransactionProcessingDetails.getEmitterBankAccount().getBalance().minus(Money.of(euroAmount)); + } +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalAmountServicePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalAmountServicePort.java new file mode 100644 index 00000000..99045304 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalAmountServicePort.java @@ -0,0 +1,5 @@ +package com.cdx.bas.domain.bank.transaction.category.digital; + +public interface DigitalAmountServicePort { + void transferBetweenAccounts(DigitalTransactionProcessingDetails digitalTransactionProcessingDetails); +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalTransactionProcessingDetails.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalTransactionProcessingDetails.java new file mode 100644 index 00000000..6fedf7db --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/DigitalTransactionProcessingDetails.java @@ -0,0 +1,53 @@ +package com.cdx.bas.domain.bank.transaction.category.digital; + +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.transaction.Transaction; + +import java.util.Map; + + +public class DigitalTransactionProcessingDetails { + Transaction transaction; + BankAccount emitterBankAccount; + BankAccount receiverBankAccount; + Map metadata; + + public DigitalTransactionProcessingDetails(Transaction transaction, BankAccount emitterBankAccount, BankAccount receiverBankAccount, Map metadata) { + this.transaction = transaction; + this.emitterBankAccount = emitterBankAccount; + this.receiverBankAccount = receiverBankAccount; + this.metadata = metadata; + } + + public Transaction getTransaction() { + return transaction; + } + + public void setTransaction(Transaction transaction) { + this.transaction = transaction; + } + + public BankAccount getEmitterBankAccount() { + return emitterBankAccount; + } + + public void setEmitterBankAccount(BankAccount emitterBankAccount) { + this.emitterBankAccount = emitterBankAccount; + } + + public BankAccount getReceiverBankAccount() { + return receiverBankAccount; + } + + public void setReceiverBankAccount(BankAccount receiverBankAccount) { + this.receiverBankAccount = receiverBankAccount; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionType.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/TransactionType.java similarity index 51% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionType.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/TransactionType.java index 61236bd8..20fe1f7c 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionType.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/TransactionType.java @@ -1,4 +1,4 @@ -package com.cdx.bas.domain.bank.transaction.type; +package com.cdx.bas.domain.bank.transaction.category.digital.type; public enum TransactionType { CREDIT, DEBIT, DEPOSIT, WITHDRAW diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/TypeValidator.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/TypeValidator.java similarity index 88% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/TypeValidator.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/TypeValidator.java index 9932f141..177274a3 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/TypeValidator.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/TypeValidator.java @@ -1,6 +1,5 @@ -package com.cdx.bas.domain.bank.transaction.validation.validator; +package com.cdx.bas.domain.bank.transaction.category.digital.type; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; @@ -28,7 +27,7 @@ public boolean isValid(TransactionType value, ConstraintValidatorContext context context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; - }; + } return true; } } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/ValidType.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/ValidType.java similarity index 82% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/ValidType.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/ValidType.java index 6b27fe14..ceb0e49e 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/ValidType.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/ValidType.java @@ -1,6 +1,5 @@ -package com.cdx.bas.domain.bank.transaction.validation.validator; +package com.cdx.bas.domain.bank.transaction.category.digital.type; -import com.cdx.bas.domain.bank.transaction.type.TransactionType; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImpl.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImpl.java new file mode 100644 index 00000000..65f8c882 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImpl.java @@ -0,0 +1,33 @@ +package com.cdx.bas.domain.bank.transaction.category.digital.type.credit; + +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalAmountServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.currency.rate.ExchangeRateUtils; +import com.cdx.bas.domain.money.Money; +import jakarta.enterprise.context.ApplicationScoped; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.message.CommonMessages.EURO_AMOUNT_DETAIL; +import static com.cdx.bas.domain.message.MessageFormatter.format; +import static com.cdx.bas.domain.money.AmountUtils.isNotPositive; + +@ApplicationScoped +public class CreditAmountServiceImpl implements DigitalAmountServicePort { + @Override + public void transferBetweenAccounts(DigitalTransactionProcessingDetails digitalTransactionProcessingDetails) { + BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(digitalTransactionProcessingDetails.getTransaction().getCurrency(), + digitalTransactionProcessingDetails.getTransaction().getAmount()); + if (isNotPositive(euroAmount)) { + throw new TransactionException(format(CREDIT_TRANSACTION_CONTEXT, CREDIT_ACTION, FAILED_STATUS, + Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), + List.of(TRANSACTION_ID_DETAIL + digitalTransactionProcessingDetails.getTransaction().getId(), EURO_AMOUNT_DETAIL + euroAmount))); + } + digitalTransactionProcessingDetails.getEmitterBankAccount().getBalance().minus(Money.of(euroAmount)); + digitalTransactionProcessingDetails.getReceiverBankAccount().getBalance().plus(Money.of(euroAmount)); + } +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImpl.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImpl.java new file mode 100644 index 00000000..b908e850 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImpl.java @@ -0,0 +1,33 @@ +package com.cdx.bas.domain.bank.transaction.category.digital.type.debit; + +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalAmountServicePort; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.currency.rate.ExchangeRateUtils; +import com.cdx.bas.domain.money.Money; +import jakarta.enterprise.context.ApplicationScoped; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import static com.cdx.bas.domain.message.CommonMessages.*; +import static com.cdx.bas.domain.message.MessageFormatter.format; +import static com.cdx.bas.domain.money.AmountUtils.isNotPositive; + +@ApplicationScoped +public class DebitAmountServiceImpl implements DigitalAmountServicePort { + + @Override + public void transferBetweenAccounts(DigitalTransactionProcessingDetails digitalTransactionProcessingDetails) { + BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(digitalTransactionProcessingDetails.getTransaction().getCurrency(), + digitalTransactionProcessingDetails.getTransaction().getAmount()); + if (isNotPositive(euroAmount)) { + throw new TransactionException(format(DEBIT_TRANSACTION_CONTEXT, DEBIT_ACTION, FAILED_STATUS, + Optional.of(SHOULD_HAVE_POSITIVE_VALUE_CAUSE), + List.of(TRANSACTION_ID_DETAIL + digitalTransactionProcessingDetails.getTransaction().getId(), EURO_AMOUNT_DETAIL + euroAmount))); + } + digitalTransactionProcessingDetails.getReceiverBankAccount().getBalance().minus(Money.of(euroAmount)); + digitalTransactionProcessingDetails.getEmitterBankAccount().getBalance().plus(Money.of(euroAmount)); + } +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/AdvancedGroup.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/AdvancedGroup.java new file mode 100644 index 00000000..f265f119 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/AdvancedGroup.java @@ -0,0 +1,4 @@ +package com.cdx.bas.domain.bank.transaction.category.group; + +public interface AdvancedGroup { +} diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/DigitalTransactionGroup.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/DigitalTransactionGroup.java similarity index 65% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/DigitalTransactionGroup.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/DigitalTransactionGroup.java index 411a0770..4cfd716e 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/DigitalTransactionGroup.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/DigitalTransactionGroup.java @@ -1,4 +1,4 @@ -package com.cdx.bas.domain.bank.transaction.validation.group; +package com.cdx.bas.domain.bank.transaction.category.group; /** * Transaction group that move money from an account to another diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/ExistingTransactionGroup.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/ExistingTransactionGroup.java similarity index 62% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/ExistingTransactionGroup.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/ExistingTransactionGroup.java index 98a2cd6d..6167f8fb 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/ExistingTransactionGroup.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/ExistingTransactionGroup.java @@ -1,4 +1,4 @@ -package com.cdx.bas.domain.bank.transaction.validation.group; +package com.cdx.bas.domain.bank.transaction.category.group; /** * Transaction group for existing transactions diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/NewTransactionGroup.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/NewTransactionGroup.java similarity index 59% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/NewTransactionGroup.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/NewTransactionGroup.java index 066faf32..ccca4ff2 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/NewTransactionGroup.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/NewTransactionGroup.java @@ -1,4 +1,4 @@ -package com.cdx.bas.domain.bank.transaction.validation.group; +package com.cdx.bas.domain.bank.transaction.category.group; /** * Transaction group for new transactions diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/PhysicalCashTransactionGroup.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/PhysicalCashTransactionGroup.java similarity index 67% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/PhysicalCashTransactionGroup.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/PhysicalCashTransactionGroup.java index 22c72cea..c2996f1e 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/PhysicalCashTransactionGroup.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/category/group/PhysicalCashTransactionGroup.java @@ -1,4 +1,4 @@ -package com.cdx.bas.domain.bank.transaction.validation.group; +package com.cdx.bas.domain.bank.transaction.category.group; /** * Transaction group that use cash money to transfer to an account diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/StatusValidator.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/StatusValidator.java similarity index 86% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/StatusValidator.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/StatusValidator.java index b2d82550..1b5a49b7 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/StatusValidator.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/StatusValidator.java @@ -1,6 +1,5 @@ -package com.cdx.bas.domain.bank.transaction.validation.validator; +package com.cdx.bas.domain.bank.transaction.status; -import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; @@ -21,7 +20,7 @@ public boolean isValid(TransactionStatus value, ConstraintValidatorContext conte context.buildConstraintViolationWithTemplate(message) .addConstraintViolation(); return false; - }; + } return true; } } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/TransactionStatusServicePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/TransactionStatusServicePort.java index 8ce9aefc..5612b4cd 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/TransactionStatusServicePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/TransactionStatusServicePort.java @@ -5,6 +5,7 @@ import java.util.Map; public interface TransactionStatusServicePort { + /** * Set transaction to outstanding with additional metadata and avoid multiple process * diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/ValidStatus.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/ValidStatus.java similarity index 69% rename from domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/ValidStatus.java rename to domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/ValidStatus.java index 0885fb0d..41e0798d 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/validator/ValidStatus.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/status/ValidStatus.java @@ -1,7 +1,5 @@ -package com.cdx.bas.domain.bank.transaction.validation.validator; +package com.cdx.bas.domain.bank.transaction.status; -import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; -import com.cdx.bas.domain.bank.transaction.validation.validator.StatusValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionProcessorServicePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionProcessorServicePort.java deleted file mode 100644 index 6341bc9b..00000000 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/type/TransactionProcessorServicePort.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cdx.bas.domain.bank.transaction.type; - -import com.cdx.bas.domain.bank.transaction.Transaction; - -public interface TransactionProcessorServicePort { - - /** - * 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/bank/transaction/validation/group/AdvancedGroup.java b/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/AdvancedGroup.java deleted file mode 100644 index b6f62360..00000000 --- a/domain/src/main/java/com/cdx/bas/domain/bank/transaction/validation/group/AdvancedGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cdx.bas.domain.bank.transaction.validation.group; - -public interface AdvancedGroup { -} diff --git a/domain/src/main/java/com/cdx/bas/domain/currency/error/CurrencyException.java b/domain/src/main/java/com/cdx/bas/domain/currency/error/CurrencyException.java index bce13746..972fd4fc 100644 --- a/domain/src/main/java/com/cdx/bas/domain/currency/error/CurrencyException.java +++ b/domain/src/main/java/com/cdx/bas/domain/currency/error/CurrencyException.java @@ -1,11 +1,17 @@ package com.cdx.bas.domain.currency.error; + +import com.cdx.bas.domain.testing.Generated; + import java.io.Serial; public class CurrencyException extends RuntimeException { @Serial private static final long serialVersionUID = 4661867226831802143L; + + @Generated public CurrencyException(String errorMessage) { super(errorMessage); } } + diff --git a/domain/src/main/java/com/cdx/bas/domain/currency/rate/ExchangeRateUtils.java b/domain/src/main/java/com/cdx/bas/domain/currency/rate/ExchangeRateUtils.java index ea509f96..e25ba4fe 100644 --- a/domain/src/main/java/com/cdx/bas/domain/currency/rate/ExchangeRateUtils.java +++ b/domain/src/main/java/com/cdx/bas/domain/currency/rate/ExchangeRateUtils.java @@ -1,17 +1,19 @@ package com.cdx.bas.domain.currency.rate; import com.cdx.bas.domain.currency.error.CurrencyException; -import lombok.experimental.UtilityClass; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; -@UtilityClass public class ExchangeRateUtils { public static final String PIVOT_CURRENCY = "EUR"; private static final Double NO_EXCHANGE_RATE_VALUE = 1.0; + private ExchangeRateUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + /** * Euro Exchange Rates for each currency * diff --git a/domain/src/main/java/com/cdx/bas/domain/currency/validation/CurrencyValidator.java b/domain/src/main/java/com/cdx/bas/domain/currency/validation/CurrencyValidator.java index c0d9202d..16b035e8 100644 --- a/domain/src/main/java/com/cdx/bas/domain/currency/validation/CurrencyValidator.java +++ b/domain/src/main/java/com/cdx/bas/domain/currency/validation/CurrencyValidator.java @@ -7,10 +7,6 @@ public class CurrencyValidator implements ConstraintValidator { - @Override - public void initialize(ValidCurrency constraintAnnotation) { - } - @Override public boolean isValid(String currency, ConstraintValidatorContext context) { return ExchangeRateUtils.hasCurrency(currency); diff --git a/domain/src/main/java/com/cdx/bas/domain/currency/validation/ValidCurrency.java b/domain/src/main/java/com/cdx/bas/domain/currency/validation/ValidCurrency.java index a00338cf..a214b99b 100644 --- a/domain/src/main/java/com/cdx/bas/domain/currency/validation/ValidCurrency.java +++ b/domain/src/main/java/com/cdx/bas/domain/currency/validation/ValidCurrency.java @@ -1,8 +1,8 @@ package com.cdx.bas.domain.currency.validation; -import com.cdx.bas.domain.currency.validation.CurrencyValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; + import java.lang.annotation.*; @Target({ElementType.FIELD}) diff --git a/domain/src/main/java/com/cdx/bas/domain/exception/DomainException.java b/domain/src/main/java/com/cdx/bas/domain/exception/DomainException.java index e44d248a..10b67a32 100644 --- a/domain/src/main/java/com/cdx/bas/domain/exception/DomainException.java +++ b/domain/src/main/java/com/cdx/bas/domain/exception/DomainException.java @@ -1,11 +1,16 @@ package com.cdx.bas.domain.exception; + +import com.cdx.bas.domain.testing.Generated; + public class DomainException extends RuntimeException { + @Generated public DomainException(String errorMessage) { super(errorMessage); } + @Generated public DomainException(String message, Throwable cause) { super(message, cause); } diff --git a/domain/src/main/java/com/cdx/bas/domain/message/CommonMessages.java b/domain/src/main/java/com/cdx/bas/domain/message/CommonMessages.java index 7944d185..053959fa 100644 --- a/domain/src/main/java/com/cdx/bas/domain/message/CommonMessages.java +++ b/domain/src/main/java/com/cdx/bas/domain/message/CommonMessages.java @@ -1,9 +1,13 @@ package com.cdx.bas.domain.message; -import lombok.experimental.UtilityClass; +import com.cdx.bas.domain.testing.Generated; -@UtilityClass +@Generated public class CommonMessages { + private CommonMessages() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + // Context public static final String SCHEDULER_CONTEXT = "Scheduler:"; public static final String BANK_ACCOUNT_CONTEXT = "Bank account:"; @@ -28,6 +32,7 @@ public class CommonMessages { public static final String DIGITAL_TRANSACTION_ACTION = "digital transaction"; public static final String DEPOSIT_ACTION = "deposit"; public static final String WITHDRAW_ACTION = "withdraw"; + public static final String CASH_TRANSACTION_ACTION = "cash transaction"; public static final String OUTSTANDING_STATUS_ACTION = "set status to outstanding"; public static final String CHANGE_STATUS_ACTION = "set status"; public static final String JSON_PARSE_METADATA = "parse JSON metadata to Map"; @@ -47,7 +52,9 @@ public class CommonMessages { // Cause public static final String NOT_FOUND_CAUSE = "not found"; + public static final String MISSING_ID_CAUSE = "missing id"; public static final String TRANSACTION_ERROR_CAUSE = "transaction error"; + public static final String DOMAIN_ERROR = "domain error"; public static final String BANK_ACCOUNT_ERROR_CAUSE = "bank account error"; public static final String SHOULD_HAVE_POSITIVE_VALUE_CAUSE = "should have positive value"; public static final String UNEXPECTED_ERROR_CAUSE = "unexpected error"; diff --git a/domain/src/main/java/com/cdx/bas/domain/message/MessageFormatter.java b/domain/src/main/java/com/cdx/bas/domain/message/MessageFormatter.java index feaffd41..c7026d40 100644 --- a/domain/src/main/java/com/cdx/bas/domain/message/MessageFormatter.java +++ b/domain/src/main/java/com/cdx/bas/domain/message/MessageFormatter.java @@ -1,11 +1,10 @@ package com.cdx.bas.domain.message; -import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; import java.util.Collections; import java.util.List; import java.util.Optional; -import org.apache.commons.lang3.StringUtils; /** * Utility class for formatting standardized messages used in various operations, following the structure: @@ -13,9 +12,12 @@ * Each optional detail appears on a new line. * This class enables consistent and clear formatting for logs and error messages across the application. */ -@UtilityClass public class MessageFormatter { + private MessageFormatter() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + /** * A constant for a single space character used to separate message components. */ @@ -33,7 +35,7 @@ public class MessageFormatter { * @param details an optional list of extra details, each appearing on a new line * @return a formatted message string based on the provided inputs */ - public String format(String context, String action, String status, Optional optionalCause, List details) { + public static String format(String context, String action, String status, Optional optionalCause, List details) { StringBuilder message = new StringBuilder(); message.append(context) .append(StringUtils.SPACE) @@ -61,7 +63,7 @@ public String format(String context, String action, String status, Optional optionalCause) { + public static String format(String context, String action, String status, Optional optionalCause) { return format(context, action, status, optionalCause, Collections.emptyList()); } @@ -74,7 +76,7 @@ public String format(String context, String action, String status, Optional details) { + public static String format(String context, String action, String status, List details) { return format(context, action, status, Optional.empty(), details); } @@ -86,7 +88,7 @@ public String format(String context, String action, String status, List * @param status the outcome of the action (e.g., "success") * @return a formatted message string with just context, action, and status */ - public String format(String context, String action, String status) { + public static String format(String context, String action, String status) { return format(context, action, status, Optional.empty(), Collections.emptyList()); } } diff --git a/domain/src/main/java/com/cdx/bas/domain/metadata/MetadataFieldNames.java b/domain/src/main/java/com/cdx/bas/domain/metadata/MetadataFieldNames.java index 18d1316a..e0837806 100644 --- a/domain/src/main/java/com/cdx/bas/domain/metadata/MetadataFieldNames.java +++ b/domain/src/main/java/com/cdx/bas/domain/metadata/MetadataFieldNames.java @@ -1,12 +1,19 @@ package com.cdx.bas.domain.metadata; -import lombok.experimental.UtilityClass; +import com.cdx.bas.domain.testing.Generated; -@UtilityClass +@Generated public class MetadataFieldNames { + + private MetadataFieldNames() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + public static final String EMITTER_AMOUNT_BEFORE_KEY = "emitter_amount_before"; public static final String EMITTER_AMOUNT_AFTER_KEY = "emitter_amount_after"; public static final String RECEIVER_AMOUNT_BEFORE_KEY = "receiver_amount_before"; public static final String RECEIVER_AMOUNT_AFTER_KEY = "receiver_amount_after"; + public static final String REMOVED_EMITTER_ID = "removed_emitter_id:"; + public static final String REMOVED_RECEIVER_ID = "removed_receiver_id:"; public static final String ERROR_KEY = "error"; } diff --git a/domain/src/main/java/com/cdx/bas/domain/money/AmountUtils.java b/domain/src/main/java/com/cdx/bas/domain/money/AmountUtils.java index 046bf0e4..f79b9255 100644 --- a/domain/src/main/java/com/cdx/bas/domain/money/AmountUtils.java +++ b/domain/src/main/java/com/cdx/bas/domain/money/AmountUtils.java @@ -1,11 +1,13 @@ package com.cdx.bas.domain.money; -import lombok.experimental.UtilityClass; - import java.math.BigDecimal; -@UtilityClass public class AmountUtils { + + private AmountUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + public static boolean isNotPositive(BigDecimal euroAmount) { return euroAmount.signum() <= 0; } diff --git a/domain/src/main/java/com/cdx/bas/domain/money/AmountValidator.java b/domain/src/main/java/com/cdx/bas/domain/money/AmountValidator.java index 05dd1234..a0fcd780 100644 --- a/domain/src/main/java/com/cdx/bas/domain/money/AmountValidator.java +++ b/domain/src/main/java/com/cdx/bas/domain/money/AmountValidator.java @@ -20,10 +20,8 @@ public void initialize(Amount constraintAnnotation) { @Override public boolean isValid(Money money, ConstraintValidatorContext context) { - if (money != null && (money.getAmount().compareTo(min) >= 0 && money.getAmount().compareTo(max) <= 0)) { - return true; - } - return false; + return money != null + && (money.getAmount().compareTo(min) >= 0 && money.getAmount().compareTo(max) <= 0); } } diff --git a/domain/src/main/java/com/cdx/bas/domain/testing/Generated.java b/domain/src/main/java/com/cdx/bas/domain/testing/Generated.java new file mode 100644 index 00000000..0756da69 --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/testing/Generated.java @@ -0,0 +1,14 @@ +package com.cdx.bas.domain.testing; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target({TYPE, METHOD, CONSTRUCTOR}) +public @interface Generated { +} diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/account/BankAccountFactoryTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/account/BankAccountFactoryTest.java index a92d2090..db62338c 100644 --- a/domain/src/test/java/com/cdx/bas/domain/bank/account/BankAccountFactoryTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/bank/account/BankAccountFactoryTest.java @@ -1,20 +1,17 @@ package com.cdx.bas.domain.bank.account; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; - import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; import com.cdx.bas.domain.bank.account.mma.MMABankAccount; import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; import com.cdx.bas.domain.bank.account.type.AccountType; - import org.junit.jupiter.api.Test; -import io.quarkus.test.junit.QuarkusTest; - import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + class BankAccountFactoryTest { @Test diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionTest.java new file mode 100644 index 00000000..55fa19bb --- /dev/null +++ b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionTest.java @@ -0,0 +1,70 @@ +package com.cdx.bas.domain.bank.transaction; + +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TransactionTest { + + @Test + void testCompareTo() { + // Arrange + Instant now = Instant.now(); + Instant earlier = now.minusSeconds(3600); // 1 hour earlier + Instant later = now.plusSeconds(3600); // 1 hour later + + Transaction transaction1 = new Transaction(); + transaction1.setId(1L); + transaction1.setEmitterAccountId(101L); + transaction1.setReceiverAccountId(102L); + transaction1.setAmount(new BigDecimal("100")); + transaction1.setCurrency("USD"); + transaction1.setType(TransactionType.CREDIT); + transaction1.setStatus(TransactionStatus.UNPROCESSED); + transaction1.setDate(now); + transaction1.setLabel("Transaction 1"); + transaction1.setMetadata(new HashMap<>()); + + Transaction transactionEarlier = new Transaction(); + transactionEarlier.setId(2L); + transactionEarlier.setEmitterAccountId(101L); + transactionEarlier.setReceiverAccountId(102L); + transactionEarlier.setAmount(new BigDecimal("200")); + transactionEarlier.setCurrency("USD"); + transactionEarlier.setType(TransactionType.DEBIT); + transactionEarlier.setStatus(TransactionStatus.UNPROCESSED); + transactionEarlier.setDate(earlier); + transactionEarlier.setLabel("Transaction Earlier"); + transactionEarlier.setMetadata(new HashMap<>()); + + Transaction transactionLater = new Transaction(); + transactionLater.setId(3L); + transactionLater.setEmitterAccountId(101L); + transactionLater.setReceiverAccountId(102L); + transactionLater.setAmount(new BigDecimal("300")); + transactionLater.setCurrency("USD"); + transactionLater.setType(TransactionType.CREDIT); + transactionLater.setStatus(TransactionStatus.UNPROCESSED); + transactionLater.setDate(later); + transactionLater.setLabel("Transaction Later"); + transactionLater.setMetadata(new HashMap<>()); + + + // Act & Assert + // transaction1 is later than transactionEarlier + assertTrue(transaction1.compareTo(transactionEarlier) > 0, "transaction1 should be after transactionEarlier"); + + // transaction1 is earlier than transactionLater + assertTrue(transaction1.compareTo(transactionLater) < 0, "transaction1 should be before transactionLater"); + + // transaction1 is equal to itself + assertEquals(0, transaction1.compareTo(transaction1), "transaction1 should be equal to itself"); + } +} \ No newline at end of file diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionValidatorTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionValidatorTest.java index 7eae78cb..06ed53db 100644 --- a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionValidatorTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/TransactionValidatorTest.java @@ -1,6 +1,7 @@ package com.cdx.bas.domain.bank.transaction; -import com.cdx.bas.domain.bank.transaction.validation.validator.TransactionValidator; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.bank.transaction.status.TransactionStatus; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; @@ -12,9 +13,6 @@ import java.util.List; import java.util.Map; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.ERROR; -import static com.cdx.bas.domain.bank.transaction.status.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.bank.transaction.type.TransactionType.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; @@ -25,30 +23,28 @@ class TransactionValidatorTest { TransactionValidator transactionValidator; @Test - public void shouldDoNothing_whenNewDigitalTransactionIsValid() { + void shouldDoNothing_whenNewDigitalTransactionIsValid() { // Arrange - Transaction creditTransaction = Transaction.builder() - .id(null) - .emitterAccountId(1L) - .receiverAccountId(2L) - .amount(new BigDecimal("1")) - .currency("EUR") - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("new transaction") - .metadata(new HashMap<>()) - .build(); + Transaction creditTransaction = new Transaction(); + creditTransaction.setId(null); + creditTransaction.setEmitterAccountId(1L); + creditTransaction.setReceiverAccountId(2L); + creditTransaction.setAmount(new BigDecimal("1")); + creditTransaction.setCurrency("EUR"); + creditTransaction.setType(TransactionType.CREDIT); + creditTransaction.setStatus(TransactionStatus.UNPROCESSED); + creditTransaction.setDate(Instant.now()); + creditTransaction.setLabel("new transaction"); + creditTransaction.setMetadata(new HashMap<>()); // Act transactionValidator.validateNewDigitalTransaction(creditTransaction); } @Test - public void shouldThrowTransactionExceptionWithMissingViolation_whenNewDigitalTransactionIsEmpty() { + void shouldThrowTransactionExceptionWithMissingViolation_whenNewDigitalTransactionIsEmpty() { // Arrange - Transaction creditTransaction = Transaction.builder() - .build(); + Transaction creditTransaction = new Transaction(); try { // Act transactionValidator.validateNewDigitalTransaction(creditTransaction); @@ -56,7 +52,6 @@ public void shouldThrowTransactionExceptionWithMissingViolation_whenNewDigitalTr } catch (TransactionException transactionException) { // Assert List expectedErrors = List.of("Date must not be null.", - "Metadata must not be null.", "Amount must not be null.", "Emitter account id must not be null.", "Currency must not be null.", @@ -66,23 +61,23 @@ public void shouldThrowTransactionExceptionWithMissingViolation_whenNewDigitalTr "Status must not be null."); List actualErrors = Arrays.stream(transactionException.getMessage().split("\\r?\\n")).toList(); assertThat(actualErrors) - .hasSize(9) + .hasSize(8) .containsExactlyInAnyOrderElementsOf(expectedErrors); } } @Test - public void shouldThrowTransactionExceptionWithWrongValue_whenNewDigitalTransactionHasWrongValue() { + void shouldThrowTransactionExceptionWithWrongValue_whenNewDigitalTransactionHasWrongValue() { // Arrange - Transaction creditTransaction = Transaction.builder() - .id(1L) - .emitterAccountId(-1L) - .receiverAccountId(-1L) - .amount(new BigDecimal("0")) - .currency("NFC") - .type(DEPOSIT) - .status(ERROR) - .build(); + Transaction creditTransaction = new Transaction(); + creditTransaction.setId(1L); + creditTransaction.setEmitterAccountId(-1L); + creditTransaction.setReceiverAccountId(-1L); + creditTransaction.setAmount(new BigDecimal("0")); + creditTransaction.setCurrency("NFC"); + creditTransaction.setType(TransactionType.DEPOSIT); + creditTransaction.setStatus(TransactionStatus.ERROR); + try { // Act transactionValidator.validateNewDigitalTransaction(creditTransaction); @@ -90,7 +85,6 @@ public void shouldThrowTransactionExceptionWithWrongValue_whenNewDigitalTransact } catch (TransactionException transactionException) { // Assert List expectedErrors = List.of("Date must not be null.", - "Metadata must not be null.", "Amount must be positive and greater than 0.", "Emitter account id must be positive.", "Currency should be in the exchange rate map.", @@ -101,35 +95,34 @@ public void shouldThrowTransactionExceptionWithWrongValue_whenNewDigitalTransact "Unexpected transaction status ERROR, expected status: UNPROCESSED."); List actualErrors = Arrays.stream(transactionException.getMessage().split("\\r?\\n")).toList(); assertThat(actualErrors) - .hasSize(10) + .hasSize(9) .containsExactlyInAnyOrderElementsOf(expectedErrors); } } @Test - public void shouldDoNothing_whenExistingDigitalTransactionIsValid() { + void shouldDoNothing_whenExistingDigitalTransactionIsValid() { // Arrange - Transaction creditTransaction = Transaction.builder() - .id(100L) - .emitterAccountId(1L) - .receiverAccountId(2L) - .amount(new BigDecimal("1")) - .currency("EUR") - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("new transaction") - .metadata(new HashMap<>()) - .build(); + Transaction creditTransaction = new Transaction(); + creditTransaction.setId(100L); + creditTransaction.setEmitterAccountId(1L); + creditTransaction.setReceiverAccountId(2L); + creditTransaction.setAmount(new BigDecimal("1")); + creditTransaction.setCurrency("EUR"); + creditTransaction.setType(TransactionType.CREDIT); + creditTransaction.setStatus(TransactionStatus.UNPROCESSED); + creditTransaction.setDate(Instant.now()); + creditTransaction.setLabel("new transaction"); + creditTransaction.setMetadata(new HashMap<>()); + // Act transactionValidator.validateExistingDigitalTransaction(creditTransaction); } @Test - public void shouldThrowTransactionException_whenExistingDigitalTransactionIsEmpty() { + void shouldThrowTransactionException_whenExistingDigitalTransactionIsEmpty() { // Arrange - Transaction creditTransaction = Transaction.builder() - .build(); + Transaction creditTransaction = new Transaction(); try { // Act transactionValidator.validateExistingDigitalTransaction(creditTransaction); @@ -137,7 +130,6 @@ public void shouldThrowTransactionException_whenExistingDigitalTransactionIsEmpt } catch (TransactionException transactionException) { // Assert List expectedErrors = List.of("Date must not be null.", - "Metadata must not be null.", "Amount must not be null.", "Emitter account id must not be null.", "Currency must not be null.", @@ -148,21 +140,21 @@ public void shouldThrowTransactionException_whenExistingDigitalTransactionIsEmpt "Receiver account id must not be null."); List actualErrors = Arrays.stream(transactionException.getMessage().split("\\r?\\n")).toList(); assertThat(actualErrors) - .hasSize(10) + .hasSize(9) .containsExactlyInAnyOrderElementsOf(expectedErrors); } } @Test - public void shouldThrowTransactionExceptionWithWrongValue_whenExistingDigitalTransactionHasWrongValue() { + void shouldThrowTransactionExceptionWithWrongValue_whenExistingDigitalTransactionHasWrongValue() { // Arrange - Transaction creditTransaction = Transaction.builder() - .id(-1L) - .emitterAccountId(-1L) - .receiverAccountId(-1L) - .amount(new BigDecimal("0")) - .currency("NFC") - .type(DEPOSIT) - .build(); + Transaction creditTransaction = new Transaction(); + creditTransaction.setId(-1L); + creditTransaction.setEmitterAccountId(-1L); + creditTransaction.setReceiverAccountId(-1L); + creditTransaction.setAmount(new BigDecimal("0")); + creditTransaction.setCurrency("NFC"); + creditTransaction.setType(TransactionType.DEPOSIT); + try { // Act transactionValidator.validateExistingDigitalTransaction(creditTransaction); @@ -170,7 +162,6 @@ public void shouldThrowTransactionExceptionWithWrongValue_whenExistingDigitalTra } catch (TransactionException transactionException) { // Assert List expectedErrors = List.of("Date must not be null.", - "Metadata must not be null.", "Amount must be positive and greater than 0.", "Emitter account id must be positive.", "Currency should be in the exchange rate map.", @@ -181,35 +172,34 @@ public void shouldThrowTransactionExceptionWithWrongValue_whenExistingDigitalTra "Receiver account id must be positive."); List actualErrors = Arrays.stream(transactionException.getMessage().split("\\r?\\n")).toList(); assertThat(actualErrors) - .hasSize(10) + .hasSize(9) .containsExactlyInAnyOrderElementsOf(expectedErrors); } } @Test - public void shouldDoNothing_whenCashTransactionIsValid() { + void shouldDoNothing_whenCashTransactionIsValid() { // Arrange Map metadata = new HashMap<>(); metadata.put("bill", "5,5"); - Transaction creditTransaction = Transaction.builder() - .emitterAccountId(1L) - .amount(new BigDecimal("10")) - .currency("EUR") - .type(WITHDRAW) - .status(UNPROCESSED) - .date(Instant.now()) - .label("new transaction") - .metadata(metadata) - .build(); + Transaction creditTransaction = new Transaction(); + creditTransaction.setEmitterAccountId(1L); + creditTransaction.setAmount(new BigDecimal("10")); + creditTransaction.setCurrency("EUR"); + creditTransaction.setType(TransactionType.WITHDRAW); + creditTransaction.setStatus(TransactionStatus.UNPROCESSED); + creditTransaction.setDate(Instant.now()); + creditTransaction.setLabel("new transaction"); + creditTransaction.setMetadata(metadata); + // Act transactionValidator.validateCashTransaction(creditTransaction); } @Test - public void shouldThrowTransactionException_whenNewCashTransactionIsEmpty() { + void shouldThrowTransactionException_whenNewCashTransactionIsEmpty() { // Arrange - Transaction creditTransaction = Transaction.builder() - .build(); + Transaction creditTransaction = new Transaction(); try { // Act transactionValidator.validateCashTransaction(creditTransaction); @@ -217,8 +207,8 @@ public void shouldThrowTransactionException_whenNewCashTransactionIsEmpty() { } catch (TransactionException transactionException) { // Assert List expectedErrors = List.of("Date must not be null.", - "Metadata must not be null.", "Amount must not be null.", + "Bill must be define for cash movements.", "Emitter account id must not be null.", "Currency must not be null.", "Label must not be null.", @@ -232,17 +222,17 @@ public void shouldThrowTransactionException_whenNewCashTransactionIsEmpty() { } @Test - public void shouldThrowTransactionException_whenNewCashTransactionHasWrongValue() { + void shouldThrowTransactionException_whenNewCashTransactionHasWrongValue() { // Arrange - Transaction creditTransaction = Transaction.builder() - .id(-1L) - .emitterAccountId(-1L) - .receiverAccountId(-1L) - .amount(new BigDecimal("0")) - .currency("NFC") - .type(DEBIT) - .status(ERROR) - .build(); + Transaction creditTransaction = new Transaction(); + creditTransaction.setId(-1L); + creditTransaction.setEmitterAccountId(-1L); + creditTransaction.setReceiverAccountId(-1L); + creditTransaction.setAmount(new BigDecimal("0")); + creditTransaction.setCurrency("NFC"); + creditTransaction.setType(TransactionType.DEBIT); + creditTransaction.setStatus(TransactionStatus.ERROR); + try { // Act transactionValidator.validateCashTransaction(creditTransaction); @@ -250,7 +240,7 @@ public void shouldThrowTransactionException_whenNewCashTransactionHasWrongValue( } catch (TransactionException transactionException) { // Assert List expectedErrors = List.of("Date must not be null.", - "Metadata must not be null.", + "Bill must be define for cash movements.", "Amount must be greater than 10 for cash movement.", "Emitter account id must be positive.", "Currency should be in the exchange rate map.", diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImplTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImplTest.java new file mode 100644 index 00000000..0043b8e5 --- /dev/null +++ b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/deposit/DepositAmountServiceImplTest.java @@ -0,0 +1,77 @@ +package com.cdx.bas.domain.bank.transaction.category.cash.type.deposit; + +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.money.Money; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +class DepositAmountServiceImplTest { + + private final DepositAmountServiceImpl depositAmountService = new DepositAmountServiceImpl(); + + @Test + void shouldApplyToAccount_whenDepositProcessorWithPositiveAmount() { + // Arrange + Transaction transaction = new Transaction(); + transaction.setId(1L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEBIT); + transaction.setEmitterAccountId(1L); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("100.0")); + transaction.setId(1L); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("0.0"))); + + CashTransactionProcessingDetails cashTransactionProcessingDetails = new CashTransactionProcessingDetails(transaction, emitterBankAccount, new HashMap<>()); + + // Act + depositAmountService.applyToAccount(cashTransactionProcessingDetails); + + // Assert + assertThat(emitterBankAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("100.0"))); + } + + @Test + void shouldThrowTransactionException_whenDepositProcessorWithNegativeAmount() { + // Arrange + Transaction transaction = new Transaction(); + long transactionId = 1L; + transaction.setId(transactionId); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEBIT); + transaction.setEmitterAccountId(transactionId); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("-100.0")); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("0.0"))); + + CashTransactionProcessingDetails cashTransactionProcessingDetails = new CashTransactionProcessingDetails(transaction, emitterBankAccount, new HashMap<>()); + + // Act + try { + depositAmountService.applyToAccount(cashTransactionProcessingDetails); + fail("should have a positive value or fail"); + } catch (TransactionException exception) { + // Assert + assertThat(exception.getMessage()).contains("Deposit transaction: deposit failed - should have positive value\nTransaction id:"+ transactionId + + "\nEuro amount:" + transaction.getAmount()); + } + } +} \ No newline at end of file diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImplTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImplTest.java new file mode 100644 index 00000000..b3456e10 --- /dev/null +++ b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/cash/type/withdraw/WithdrawAmountServiceImplTest.java @@ -0,0 +1,72 @@ +package com.cdx.bas.domain.bank.transaction.category.cash.type.withdraw; + +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.cash.CashTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.money.Money; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +class WithdrawAmountServiceImplTest { + + private final WithdrawAmountServiceImpl withdrawAmountService = new WithdrawAmountServiceImpl(); + + @Test + void shouldApplyToAccount_whenWithdrawProcessorWithPositiveAmount() { + // Arrange + Transaction transaction = new Transaction(); + transaction.setId(1L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.WITHDRAW); + transaction.setEmitterAccountId(1L); + transaction.setAmount(new BigDecimal("100.00")); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("100.00"))); + CashTransactionProcessingDetails cashTransactionProcessingDetails = new CashTransactionProcessingDetails(transaction, emitterBankAccount, new HashMap<>()); + + // Act + withdrawAmountService.applyToAccount(cashTransactionProcessingDetails); + + // Assert + assertThat(emitterBankAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("0.00"))); + } + + @Test + void shouldThrowTransactionException_whenWithdrawProcessorWithNegativeAmount() { + // Arrange + Transaction transaction = new Transaction(); + long transactionId = 1L; + transaction.setId(transactionId); + transaction.setType(TransactionType.WITHDRAW); + transaction.setCurrency("EUR"); + transaction.setEmitterAccountId(transactionId); + transaction.setAmount(new BigDecimal("-100.0")); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("100.0"))); + CashTransactionProcessingDetails cashTransactionProcessingDetails = new CashTransactionProcessingDetails(transaction, emitterBankAccount, new HashMap<>()); + + // Act + try { + withdrawAmountService.applyToAccount(cashTransactionProcessingDetails); + fail("should have a positive value or fail"); + } catch (TransactionException exception) { + // Assert + assertThat(exception.getMessage()).contains("Withdraw transaction: withdraw failed - should have positive value\nTransaction id:"+ transactionId + + "\nEuro amount:" + transaction.getAmount()); + } + } +} \ No newline at end of file diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImplTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImplTest.java new file mode 100644 index 00000000..642b7d96 --- /dev/null +++ b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/credit/CreditAmountServiceImplTest.java @@ -0,0 +1,86 @@ + +package com.cdx.bas.domain.bank.transaction.category.digital.type.credit; + +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.money.Money; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +class CreditAmountServiceImplTest { + + private final CreditAmountServiceImpl creditAmountService = new CreditAmountServiceImpl(); + + @Test + void shouldTransferBetweenAccounts_whenCreditProcessorWithPositiveAmount() { + // Arrange + Transaction transaction = new Transaction(); + transaction.setId(1L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.CREDIT); + transaction.setEmitterAccountId(1L); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("100")); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("100.0"))); + + BankAccount receiverBankAccount = new SavingBankAccount(); + receiverBankAccount.setId(2L); + receiverBankAccount.setBalance(Money.of(new BigDecimal("0.0"))); + DigitalTransactionProcessingDetails digitalTransactionProcessingDetails = new DigitalTransactionProcessingDetails(transaction, emitterBankAccount, receiverBankAccount, new HashMap<>()); + + // Act + creditAmountService.transferBetweenAccounts(digitalTransactionProcessingDetails); + + // Assert + assertThat(emitterBankAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("0.0"))); + assertThat(receiverBankAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("100.0"))); + } + + @Test + void shouldThrowTransactionException_whenCreditProcessorWithNegativeAmount() { + // Arrange + Transaction transaction = new Transaction(); + long transactionId = 1L; + transaction.setId(transactionId); + transaction.setType(TransactionType.CREDIT); + transaction.setCurrency("EUR"); + transaction.setEmitterAccountId(transactionId); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("-100.0")); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("100.0"))); + + BankAccount receiverBankAccount = new SavingBankAccount(); + receiverBankAccount.setId(2L); + receiverBankAccount.setBalance(Money.of(new BigDecimal("0.0"))); + DigitalTransactionProcessingDetails digitalTransactionProcessingDetails = new DigitalTransactionProcessingDetails(transaction, emitterBankAccount, receiverBankAccount, new HashMap<>()); + + // Act + try { + creditAmountService.transferBetweenAccounts(digitalTransactionProcessingDetails); + fail("should have a positive value or fail"); + } catch (TransactionException exception) { + // Assert + assertThat(exception.getMessage()).contains("Credit transaction: credit failed - should have positive value\nTransaction id:"+ transactionId + + "\nEuro amount:" + transaction.getAmount()); + } + } +} \ No newline at end of file diff --git a/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImplTest.java b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImplTest.java new file mode 100644 index 00000000..fd049124 --- /dev/null +++ b/domain/src/test/java/com/cdx/bas/domain/bank/transaction/category/digital/type/debit/DebitAmountServiceImplTest.java @@ -0,0 +1,86 @@ +package com.cdx.bas.domain.bank.transaction.category.digital.type.debit; + +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.saving.SavingBankAccount; +import com.cdx.bas.domain.bank.transaction.Transaction; +import com.cdx.bas.domain.bank.transaction.TransactionException; +import com.cdx.bas.domain.bank.transaction.category.digital.DigitalTransactionProcessingDetails; +import com.cdx.bas.domain.bank.transaction.category.digital.type.TransactionType; +import com.cdx.bas.domain.money.Money; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +class DebitAmountServiceImplTest { + + private final DebitAmountServiceImpl debitAmountService = new DebitAmountServiceImpl(); + + @Test + void shouldTransferBetweenAccounts_whenDebitProcessorWithPositiveAmount() { + // Arrange + Transaction transaction = new Transaction(); + transaction.setId(1L); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEBIT); + transaction.setEmitterAccountId(1L); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("100.0")); + transaction.setId(1L); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("0.0"))); + + BankAccount receiverBankAccount = new SavingBankAccount(); + receiverBankAccount.setId(2L); + receiverBankAccount.setBalance(Money.of(new BigDecimal("100.0"))); + DigitalTransactionProcessingDetails digitalTransactionProcessingDetails = new DigitalTransactionProcessingDetails(transaction, emitterBankAccount, receiverBankAccount, new HashMap<>()); + + // Act + debitAmountService.transferBetweenAccounts(digitalTransactionProcessingDetails); + + // Assert + assertThat(emitterBankAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("100.0"))); + assertThat(receiverBankAccount.getBalance()) + .usingRecursiveComparison() + .isEqualTo(Money.of(new BigDecimal("0.0"))); + } + + @Test + void shouldThrowTransactionException_whenDebitProcessorWithNegativeAmount() { + // Arrange + Transaction transaction = new Transaction(); + long transactionId = 1L; + transaction.setId(transactionId); + transaction.setCurrency("EUR"); + transaction.setType(TransactionType.DEBIT); + transaction.setEmitterAccountId(transactionId); + transaction.setReceiverAccountId(2L); + transaction.setAmount(new BigDecimal("-100.0")); + + BankAccount emitterBankAccount = new SavingBankAccount(); + emitterBankAccount.setId(1L); + emitterBankAccount.setBalance(Money.of(new BigDecimal("0.0"))); + + BankAccount receiverBankAccount = new SavingBankAccount(); + receiverBankAccount.setId(2L); + receiverBankAccount.setBalance(Money.of(new BigDecimal("100.0"))); + DigitalTransactionProcessingDetails digitalTransactionProcessingDetails = new DigitalTransactionProcessingDetails(transaction, emitterBankAccount, receiverBankAccount, new HashMap<>()); + + // Act + try { + debitAmountService.transferBetweenAccounts(digitalTransactionProcessingDetails); + fail("should have a positive value or fail"); + } catch (TransactionException exception) { + // Assert + assertThat(exception.getMessage()).contains("Debit transaction: debit failed - should have positive value\nTransaction id:"+ transactionId + + "\nEuro amount:" + transaction.getAmount()); + } + } +} \ No newline at end of file diff --git a/domain/src/test/java/com/cdx/bas/domain/currency/validation/CurrencyValidatorTest.java b/domain/src/test/java/com/cdx/bas/domain/currency/validation/CurrencyValidatorTest.java index 3867ba8c..00469a19 100644 --- a/domain/src/test/java/com/cdx/bas/domain/currency/validation/CurrencyValidatorTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/currency/validation/CurrencyValidatorTest.java @@ -1,7 +1,33 @@ package com.cdx.bas.domain.currency.validation; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class CurrencyValidatorTest { + private CurrencyValidator currencyValidator; + + @BeforeEach + void setUp() { + currencyValidator = new CurrencyValidator(); + } + + @Test + void isValid_shouldReturnTrueForValidCurrency() { + // Test valid currencies + assertTrue(currencyValidator.isValid("USD", null), "USD should be valid"); + assertTrue(currencyValidator.isValid("EUR", null), "EUR should be valid (pivot currency)"); + assertTrue(currencyValidator.isValid("JPY", null), "JPY should be valid"); + } + + @Test + void isValid_shouldReturnFalseForInvalidCurrency() { + // Test invalid currencies + assertFalse(currencyValidator.isValid("ABC", null), "ABC should be invalid"); + assertFalse(currencyValidator.isValid("XYZ", null), "XYZ should be invalid"); + assertFalse(currencyValidator.isValid(null, null), "null should be invalid"); + } } \ No newline at end of file diff --git a/domain/src/test/java/com/cdx/bas/domain/message/MessageFormatterTest.java b/domain/src/test/java/com/cdx/bas/domain/message/MessageFormatterTest.java index aa9b3513..f315e83c 100644 --- a/domain/src/test/java/com/cdx/bas/domain/message/MessageFormatterTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/message/MessageFormatterTest.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.Optional; -import static com.cdx.bas.domain.message.CommonMessages.*; import static org.assertj.core.api.Assertions.assertThat; class MessageFormatterTest { diff --git a/domain/src/test/java/com/cdx/bas/domain/money/AmountUtilsTest.java b/domain/src/test/java/com/cdx/bas/domain/money/AmountUtilsTest.java index 821bb47a..3e69cc6d 100644 --- a/domain/src/test/java/com/cdx/bas/domain/money/AmountUtilsTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/money/AmountUtilsTest.java @@ -1,8 +1,6 @@ package com.cdx.bas.domain.money; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; diff --git a/domain/src/test/java/com/cdx/bas/domain/money/AmountValidatorTest.java b/domain/src/test/java/com/cdx/bas/domain/money/AmountValidatorTest.java index 254e248d..865cf9ff 100644 --- a/domain/src/test/java/com/cdx/bas/domain/money/AmountValidatorTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/money/AmountValidatorTest.java @@ -1,7 +1,5 @@ package com.cdx.bas.domain.money; -import com.cdx.bas.domain.money.Amount; -import com.cdx.bas.domain.money.Money; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; import jakarta.validation.ConstraintViolation; @@ -14,13 +12,13 @@ import static org.assertj.core.api.Assertions.assertThat; @QuarkusTest -public class AmountValidatorTest { +class AmountValidatorTest { @Inject Validator validator; @Test - public void isValid_shouldGeneratesConstraintValidationWithDefaultValue_whenBalanceNotValidAndMessageIsNotSpecified() { + void isValid_shouldGeneratesConstraintValidationWithDefaultValue_whenBalanceNotValidAndMessageIsNotSpecified() { Money money = new Money(new BigDecimal("10001")); DefaultValidatorTester validatorTester = new DefaultValidatorTester(); validatorTester.balance = money; @@ -32,7 +30,7 @@ public void isValid_shouldGeneratesConstraintValidationWithDefaultValue_whenBala } @Test - public void isValid_shouldGeneratesConstraintsValidation_whenAmountOfMoneyIsLowerThanMinValue() { + void isValid_shouldGeneratesConstraintsValidation_whenAmountOfMoneyIsLowerThanMinValue() { Money money = new Money(new BigDecimal("-100")); SpecifiedValidatorTester validatorTester = new SpecifiedValidatorTester(); validatorTester.balance = money; @@ -44,7 +42,7 @@ public void isValid_shouldGeneratesConstraintsValidation_whenAmountOfMoneyIsLowe } @Test - public void isValid_shouldGeneratesConstraintValidation_whenAmountOfMoneyIsGreaterThanMaxValue() { + void isValid_shouldGeneratesConstraintValidation_whenAmountOfMoneyIsGreaterThanMaxValue() { Money money = new Money(new BigDecimal("100001")); SpecifiedValidatorTester validatorTester = new SpecifiedValidatorTester(); validatorTester.balance = money; @@ -56,7 +54,7 @@ public void isValid_shouldGeneratesConstraintValidation_whenAmountOfMoneyIsGreat } @Test - public void isValid_shouldGenerateConstraintValidation_whenAmountOfMoneyIsBetweenMinAndMaxValues() { + void isValid_shouldGenerateConstraintValidation_whenAmountOfMoneyIsBetweenMinAndMaxValues() { Money money = new Money(new BigDecimal("100")); SpecifiedValidatorTester validatorTester = new SpecifiedValidatorTester(); validatorTester.balance = money; @@ -67,7 +65,7 @@ public void isValid_shouldGenerateConstraintValidation_whenAmountOfMoneyIsBetwee } @Test - public void isValid_shouldGenerateConstraintsValidationFromBothValidators_whenUsingTwoValidators() { + void isValid_shouldGenerateConstraintsValidationFromBothValidators_whenUsingTwoValidators() { Money money = new Money(new BigDecimal("100001")); DefaultValidatorTester defaultValidatorTester = new DefaultValidatorTester(); defaultValidatorTester.balance = money; diff --git a/env/init-pg.sql b/env/init-pg.sql index d933f1d8..060c5931 100644 --- a/env/init-pg.sql +++ b/env/init-pg.sql @@ -44,7 +44,7 @@ SET TIME ZONE 'Europe/Paris'; CREATE TABLE basapp.transactions ( transaction_id BIGSERIAL UNIQUE NOT NULL, - emitter_account_id bigint NOT NULL, + emitter_account_id bigint, receiver_account_id bigint, type VARCHAR(25) NOT NULL, amount DECIMAL NOT NULL, diff --git a/env/test-resources/init-test-h2.sql b/env/test-resources/init-test-h2.sql index 4bc52c93..b88bedce 100644 --- a/env/test-resources/init-test-h2.sql +++ b/env/test-resources/init-test-h2.sql @@ -43,7 +43,7 @@ SET TIME ZONE 'Europe/Paris'; CREATE TABLE IF NOT EXISTS basapp.transactions ( transaction_id BIGSERIAL UNIQUE NOT NULL, - emitter_account_id bigint NOT NULL, + emitter_account_id bigint, receiver_account_id bigint, type VARCHAR(25) NOT NULL, amount DECIMAL NOT NULL, diff --git a/env/test-resources/insert-test-h2.sql b/env/test-resources/insert-test-h2.sql index fa622691..8668e679 100644 --- a/env/test-resources/insert-test-h2.sql +++ b/env/test-resources/insert-test-h2.sql @@ -1,7 +1,7 @@ -- DB SEQ ALTER -ALTER SEQUENCE basapp.customers_customer_id_seq RESTART WITH 10; -ALTER SEQUENCE basapp.bank_accounts_account_id_seq RESTART WITH 10; -ALTER SEQUENCE basapp.transactions_transaction_id_seq RESTART WITH 10; +ALTER SEQUENCE basapp.customers_customer_id_seq RESTART WITH 20; +ALTER SEQUENCE basapp.bank_accounts_account_id_seq RESTART WITH 20; +ALTER SEQUENCE basapp.transactions_transaction_id_seq RESTART WITH 20; INSERT INTO basapp.customers(customer_id, first_name, last_name, gender, marital_status, birthday, country, address, city, email, phone_number, metadata) @@ -42,4 +42,6 @@ VALUES (1, 1, 2, 'CREDIT', 1600.00, 'EUR', 'COMPLETED', TIMESTAMP WITH TIME ZONE (6, 1, 7, 'DEBIT', 2000.00, 'EUR', 'UNPROCESSED', TIMESTAMP WITH TIME ZONE '2024-11-06 18:30:00+01:00', 'transaction 6', null), (7, 3, 1, 'CREDIT', 1000.00, 'EUR', 'UNPROCESSED', TIMESTAMP WITH TIME ZONE '2024-12-06 18:00:00+01:00', 'transaction 7', null), (8, 4, 2, 'DEBIT', 300.80, 'EUR', 'UNPROCESSED', TIMESTAMP WITH TIME ZONE '2024-12-06 19:00:00+01:00', 'transaction 8', null), - (9, 8, 7, 'DEBIT', 5000.00, 'EUR', 'UNPROCESSED', TIMESTAMP WITH TIME ZONE '2024-12-06 19:00:10+01:00', 'transaction 9', null); \ No newline at end of file + (9, 8, 7, 'DEBIT', 5000.00, 'EUR', 'UNPROCESSED', TIMESTAMP WITH TIME ZONE '2024-12-06 19:00:10+01:00', 'transaction 9', null), + (10, 1, null, 'DEPOSIT', 100.00, 'EUR', 'COMPLETED', TIMESTAMP WITH TIME ZONE '2024-12-06 19:00:10+01:00', 'transaction 10', null), + (11, 1, null, 'WITHDRAW', 200.00, 'EUR', 'COMPLETED', TIMESTAMP WITH TIME ZONE '2024-12-06 19:00:10+01:00', 'transaction 11', null); \ No newline at end of file diff --git a/pom.xml b/pom.xml index aa66725b..9d0d099d 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 3.1.5 3.11.0 3.25.3 - 1.18.30 + 1.18.34 @@ -101,7 +101,7 @@ org.projectlombok lombok - 1.18.30 + ${lombok.version} org.apache.commons