package zw.qantra.tm.domain.services; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; 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 org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import zw.qantra.tm.domain.enums.CurrencyType; import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.models.AdditionalData; import zw.qantra.tm.domain.models.Currency; import zw.qantra.tm.domain.models.Transaction; import zw.qantra.tm.domain.repositories.AdditionalDataRepository; import zw.qantra.tm.domain.repositories.TransactionRepository; import zw.qantra.tm.exceptions.ApiException; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.UUID; import static java.util.UUID.randomUUID; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class TransactionServiceTest { @Mock private TransactionRepository transactionRepository; @Mock private AdditionalDataService additionalDataService; @Mock private CurrencyService currencyService; @Mock private AdditionalDataRepository additionalDataRepository; @InjectMocks private TransactionService transactionService; private Transaction transaction; private final UUID txId = UUID.randomUUID(); @BeforeEach void setUp() { transaction = new Transaction(); transaction.setId(txId); transaction.setWorkspaceId(randomUUID()); transaction.setAmount(new BigDecimal("100.00")); transaction.setDebitCurrency(CurrencyType.USD); transaction.setCreditCurrency(CurrencyType.ZWL); lenient().when(additionalDataService.getAdditionalDataRepository()).thenReturn(additionalDataRepository); } @Test @DisplayName("Should find transaction by ID") void shouldFindById() { when(transactionRepository.findById(txId)).thenReturn(Optional.of(transaction)); AdditionalData additionalData = new AdditionalData(); additionalData.setJsonString("[{\"key\":\"value\"}]"); when(additionalDataRepository.findByTransactionIdAndRequestType( txId.toString(), RequestType.INTEGRATION)) .thenReturn(Optional.of(additionalData)); Transaction result = transactionService.findById(txId); assertNotNull(result); assertEquals(txId, result.getId()); assertNotNull(result.getAdditionalData()); } @Test @DisplayName("Should throw exception when transaction not found by ID") void shouldThrowWhenNotFound() { when(transactionRepository.findById(txId)).thenReturn(Optional.empty()); assertThrows(ApiException.class, () -> transactionService.findById(txId)); } @Test @DisplayName("Should find transaction without additional data gracefully") void shouldFindByIdWithoutAdditionalData() { when(transactionRepository.findById(txId)).thenReturn(Optional.of(transaction)); when(additionalDataRepository.findByTransactionIdAndRequestType( txId.toString(), RequestType.INTEGRATION)) .thenThrow(new ApiException("Not found")); Transaction result = transactionService.findById(txId); assertNotNull(result); assertNull(result.getAdditionalData()); } @Test @DisplayName("Should return paginated transactions") void shouldFindAllPaginated() { Specification spec = mock(Specification.class); Pageable pageable = PageRequest.of(0, 10); Page page = new PageImpl<>(List.of(transaction)); when(transactionRepository.findAll(spec, pageable)).thenReturn(page); Page result = transactionService.findAll(spec, pageable); assertEquals(1, result.getTotalElements()); assertEquals(txId, result.getContent().get(0).getId()); } @Test @DisplayName("Should calculate credit amount using bank rate") void shouldCalculateCurrencyExchangeValue() { Currency currencyUsd = new Currency(); currencyUsd.setBankRate("1.0"); Currency currencyZwl = new Currency(); currencyZwl.setBankRate("15.0"); when(currencyService.findCurrencyByIsoCode("USD")).thenReturn(currencyUsd); when(currencyService.findCurrencyByIsoCode("ZWL")).thenReturn(currencyZwl); BigDecimal creditAmount = transactionService.calculateCurrencyExchangeValue( transaction.getDebitCurrency(), transaction.getCreditCurrency(), transaction.getAmount()); assertEquals(new BigDecimal("1500.0000"), creditAmount); } @Test @DisplayName("Should return zero credit amount when credit currency is null") void shouldReturnZeroWhenCreditCurrencyNull() { transaction.setCreditCurrency(null); BigDecimal result = transactionService.calculateCurrencyExchangeValue( transaction.getDebitCurrency(), transaction.getCreditCurrency(), transaction.getAmount()); assertEquals(BigDecimal.ZERO, result); } @Test @DisplayName("Should reassign transactions from old workspace to new workspace") void shouldReassignTransactions() { UUID oldWs = randomUUID(); UUID newWs = randomUUID(); List transactions = List.of(transaction); when(transactionRepository.findAllByWorkspaceId(oldWs)).thenReturn(transactions); when(transactionRepository.save(any(Transaction.class))).thenReturn(transaction); transactionService.reassignTransactions(oldWs, newWs); assertEquals(newWs, transaction.getWorkspaceId()); verify(transactionRepository).save(transaction); } @Test @DisplayName("Should save transaction") void shouldSave() { when(transactionRepository.save(transaction)).thenReturn(transaction); Transaction result = transactionService.save(transaction); assertNotNull(result); verify(transactionRepository).save(transaction); } }