diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/handler/TransferHandler.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/handler/TransferHandler.java index 036d89e24b97177dbee0d163343cb7615f714a89..473208a735cab23ff2188a2e4eae926ed532e2ff 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/handler/TransferHandler.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/handler/TransferHandler.java @@ -12,7 +12,7 @@ import java.math.BigDecimal; import java.util.Currency; /** - * Handler for transaction of type ${@link cz.muni.pa165.banking.domain.transaction.TransactionType#WITHDRAWAL}. + * Handler for transaction of type ${@link cz.muni.pa165.banking.domain.transaction.TransactionType#TRANSFER}. * Customer may send a specified amount of money to a foreign account, where the currency of both accounts does * not have to be the same. The amount of money is calculated via ${@link cz.muni.pa165.banking.domain.money.CurrencyConverter} * using the target account's currency. diff --git a/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/ProcessControllerIT.java b/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/ProcessControllerIT.java index d29c259fc538557c83181dc4c40f4acccf1e0fe7..d38303aea61a0fec25444c5d4f37550585ab50e4 100644 --- a/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/ProcessControllerIT.java +++ b/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/ProcessControllerIT.java @@ -9,6 +9,8 @@ import cz.muni.pa165.banking.domain.process.Process; import cz.muni.pa165.banking.domain.process.ProcessOperations; import cz.muni.pa165.banking.domain.process.status.Status; import cz.muni.pa165.banking.domain.process.status.StatusInformation; +import cz.muni.pa165.banking.transaction.processor.dto.ProcessStatusDto; +import cz.muni.pa165.banking.transaction.processor.dto.ProcessStatusListDto; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,10 +27,14 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import java.time.*; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -46,6 +52,9 @@ public class ProcessControllerIT { @Autowired private ObjectMapper objectMapper; + + @Autowired + private ProcessRepositoryJpa repository; @MockBean private ExchangeRatesApi apiMock; @@ -55,34 +64,45 @@ public class ProcessControllerIT { private static final Instant instant = LocalDateTime.of(2024, Month.APRIL, 24, 12, 0, 0).toInstant(ZoneOffset.UTC); + private static Set<Process> storedProcesses; + + private static Process processedProcess; + + @BeforeAll public static void initDb(@Autowired ProcessRepositoryJpa repository) { repository.save(Process.createNew()); repository.save(Process.createNew()); - Process processed1 = Process.createNew(); - ProcessOperations.changeState(processed1, new StatusInformation(instant, Status.PENDING, "PENDING")); - repository.save(processed1); + Process process1 = Process.createNew(); + ProcessOperations.changeState(process1, new StatusInformation(instant, Status.PENDING, "PENDING")); + repository.save(process1); - Process processed2 = Process.createNew(); - ProcessOperations.changeState(processed2, new StatusInformation(instant, Status.PENDING, "PENDING")); - repository.save(processed2); + Process process2 = Process.createNew(); + ProcessOperations.changeState(process2, new StatusInformation(instant, Status.PENDING, "PENDING")); + repository.save(process2); LocalDateTime currentDateTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC); LocalDateTime newDateTime = currentDateTime.minusDays(10); Instant tenDaysAgo = newDateTime.toInstant(ZoneOffset.UTC); - Process processed4 = Process.createNew(); - ProcessOperations.changeState(processed4, new StatusInformation(tenDaysAgo, Status.CREATED, "CREATED")); - repository.save(processed4); + processedProcess = Process.createNew(); + ProcessOperations.changeState(processedProcess, new StatusInformation(tenDaysAgo, Status.PROCESSED, "PROCESSED")); + repository.save(processedProcess); - Process processed5 = Process.createNew(); - ProcessOperations.changeState(processed5, new StatusInformation(tenDaysAgo, Status.PENDING, "PENDING")); - repository.save(processed5); + Process process4 = Process.createNew(); + ProcessOperations.changeState(process4, new StatusInformation(tenDaysAgo, Status.CREATED, "CREATED")); + repository.save(process4); + + Process process5 = Process.createNew(); + ProcessOperations.changeState(process5, new StatusInformation(tenDaysAgo, Status.PENDING, "PENDING")); + repository.save(process5); + + storedProcesses = Set.of(process1, process2, processedProcess, process4, process5); } - @WithMockUser(authorities = "SCOPE_test_2") @Test + @WithMockUser(authorities = "SCOPE_test_2") public void resolveStaleProcesses() throws Exception { LocalDateTime currentDateTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC); LocalDateTime newDateTime = currentDateTime.minusDays(1); @@ -91,6 +111,7 @@ public class ProcessControllerIT { List<Process> staleProcesses = service.unresolvedProcessesToDate(yesterday); for (Process process : staleProcesses) { assertNotEquals(Status.FAILED, process.getStatus()); + assertNotEquals(Status.PROCESSED, process.getStatus()); } List<UUID> staleProcessIds = staleProcesses.stream() .map(Process::getUuid) @@ -114,5 +135,61 @@ public class ProcessControllerIT { } } + @Test + @WithMockUser(authorities = "SCOPE_test_2") + public void fetchProcessedProcesses() throws Exception { + Set<UUID> expectedProcessIds = storedProcesses.stream() + .filter(p -> p.getStatus().equals(Status.PROCESSED)) + .map(Process::getUuid) + .collect(Collectors.toSet()); + + MvcResult response = mockMvc.perform(get("/processes/v1/PROCESSED") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + List<ProcessStatusDto> foundProcesses = objectMapper.readValue(jsonResponse, ProcessStatusListDto.class).getProcesses(); + + assertEquals(expectedProcessIds.size(), foundProcesses.size()); + + for (ProcessStatusDto process : foundProcesses) { + assertEquals(process.getStatus().getStatus().getValue(), "PROCESSED"); + assertTrue(expectedProcessIds.contains(process.getIdentifier())); + } + } + + @Test + @WithMockUser(authorities = "SCOPE_test_2") + public void fetchValidProcessStatus() throws Exception { + MvcResult response = mockMvc.perform(get("/process/v1/{uuid}/status", processedProcess.getUuid()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + ProcessStatusDto status = objectMapper.readValue(jsonResponse, ProcessStatusDto.class); + + assertEquals(processedProcess.getUuid(), status.getIdentifier()); + assertEquals(processedProcess.getStatus().name(), status.getStatus().getStatus().getValue()); + } + + @Test + @WithMockUser(authorities = "SCOPE_test_2") + public void fetchInvalidProcessStatus() throws Exception { + MvcResult response = mockMvc.perform(get("/process/v1/{uuid}/status", UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + LinkedHashMap JSON = objectMapper.readValue(jsonResponse, LinkedHashMap.class); + + assertTrue(JSON.containsKey("status")); + assertEquals("NOT_FOUND", JSON.get("status")); + assertTrue(JSON.containsKey("message")); + assertEquals("Entity not present in repository", JSON.get("message")); + } + } diff --git a/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/TransactionControllerIT.java b/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/TransactionControllerIT.java new file mode 100644 index 0000000000000000000000000000000000000000..e341340b63c6f6855076c69a45a8f75f983e583e --- /dev/null +++ b/transaction-processor/src/test/java/cz/muni/pa165/banking/application/integration/TransactionControllerIT.java @@ -0,0 +1,160 @@ +package cz.muni.pa165.banking.application.integration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import cz.muni.pa165.banking.account.management.AccountApi; +import cz.muni.pa165.banking.account.management.dto.AccountDto; +import cz.muni.pa165.banking.account.query.CustomerServiceApi; +import cz.muni.pa165.banking.account.query.SystemServiceApi; +import cz.muni.pa165.banking.account.query.dto.TransactionType; +import cz.muni.pa165.banking.application.proxy.rate.ExchangeRatesApi; +import cz.muni.pa165.banking.domain.account.Account; +import cz.muni.pa165.banking.domain.messaging.MessageProducer; +import cz.muni.pa165.banking.domain.money.Money; +import cz.muni.pa165.banking.domain.process.Process; +import cz.muni.pa165.banking.domain.process.ProcessFactory; +import cz.muni.pa165.banking.domain.process.repository.ProcessRepository; +import cz.muni.pa165.banking.domain.process.repository.ProcessTransactionRepository; +import cz.muni.pa165.banking.domain.transaction.Transaction; +import cz.muni.pa165.banking.transaction.processor.dto.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.LinkedHashMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureDataJpa +@ExtendWith(MockitoExtension.class) +@AutoConfigureMockMvc +public class TransactionControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ExchangeRatesApi apiMock; + + @MockBean + private SecurityFilterChain securityFilterChainMock; + + @MockBean + private AccountApi accountApi; + + @MockBean + private CustomerServiceApi publicBalanceApi; + + @MockBean + private SystemServiceApi privateBalanceApi; + + private static Process createdProcess; + + @BeforeAll + static void initDb(@Autowired ProcessRepository repository, @Autowired ProcessTransactionRepository transactionRepository) { + Account a1 = new Account("acc1"); + Account a2 = new Account("acc2"); + Money money = new Money(BigDecimal.ONE, Currency.getInstance("CZK")); + Transaction transaction = new Transaction(a1, a2, cz.muni.pa165.banking.domain.transaction.TransactionType.TRANSFER, money, "transfer 1czk"); + + MessageProducer producer = mock(MessageProducer.class); + createdProcess = new ProcessFactory(transactionRepository, repository).create(transaction, producer); + } + + @Test + @WithMockUser(authorities = "SCOPE_test_2") + public void processTransferBetweenAccounts() throws Exception { + Account source = new Account("acc_source"); + Account target = new Account("acc_target"); + + when(accountApi.findByAccountNumber(source.getAccountNumber())).thenReturn(ResponseEntity.ok(new AccountDto().number("acc_source").currency("CZK"))); + when(accountApi.findByAccountNumber(target.getAccountNumber())).thenReturn(ResponseEntity.ok(new AccountDto().number("acc_target").currency("CZK"))); + when(publicBalanceApi.getBalance(source.getAccountNumber())).thenReturn(ResponseEntity.ok(BigDecimal.valueOf(1000))); + + + String requestBody = objectMapper.writeValueAsString(new TransactionDto() + .source(new cz.muni.pa165.banking.transaction.processor.dto.AccountDto().accountNumber("acc_source")) + .target(new cz.muni.pa165.banking.transaction.processor.dto.AccountDto().accountNumber("acc_target")) + .type(TransactionTypeDto.TRANSFER) + .amount(new MoneyDto().amount(BigDecimal.valueOf(100)).currency("CZK")) + .detail("TRANSFER TEST")); + + MvcResult response = mockMvc.perform( + put("/transaction/v1/process") + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + ProcessDto process = objectMapper.readValue(jsonResponse, ProcessDto.class); + + assertEquals(StatusDto.CREATED, process.getStatus()); + + verify(privateBalanceApi).addTransactionToBalance(source.getAccountNumber(), BigDecimal.valueOf(-100), process.getIdentifier(), TransactionType.TRANSFER); + verify(privateBalanceApi).addTransactionToBalance(target.getAccountNumber(), BigDecimal.valueOf(100), process.getIdentifier(), TransactionType.TRANSFER); + } + + @Test + @WithMockUser(authorities = "SCOPE_test_2") + public void getValidProcessStatus() throws Exception { + MvcResult response = mockMvc.perform( + get("/transaction/v1/status") + .header("x-process-uuid", createdProcess.getUuid()) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + ProcessDetailDto detail = objectMapper.readValue(jsonResponse, ProcessDetailDto.class); + + assertEquals(createdProcess.getUuid(), detail.getIdentifier()); + assertEquals(StatusDto.CREATED, detail.getStatus()); + } + + @Test + @WithMockUser(authorities = "SCOPE_test_2") + public void getNonexistentProcessStatus() throws Exception { + MvcResult response = mockMvc.perform( + get("/transaction/v1/status") + .header("x-process-uuid", UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isNotFound()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + LinkedHashMap JSON = objectMapper.readValue(jsonResponse, LinkedHashMap.class); + + assertTrue(JSON.containsKey("status")); + assertEquals("NOT_FOUND", JSON.get("status")); + assertTrue(JSON.containsKey("message")); + assertEquals("Entity not present in repository", JSON.get("message")); + } + +}