From 7ec25a5330cfdaa5b4963439a4b4bfd5b83f7f5f Mon Sep 17 00:00:00 2001 From: sabexzero Date: Wed, 24 Apr 2024 21:30:35 +0300 Subject: [PATCH] The wallet service is 100% covered by tests --- build.gradle | 8 +- .../example/TradeHub/TradeHubApplication.java | 2 - .../CryptoTransactionAspect.java | 2 - .../{specified => }/user/UserRepository.java | 20 +- .../TradeHub/service/CoinApiService.java | 6 +- .../TradeHub/service/CommerceService.java | 11 +- .../service/ConvertCryptoService.java | 3 +- .../service/TransferCryptoService.java | 1 - .../TradeHub/service/WalletsService.java | 18 +- .../example/TradeHub/WalletsServiceTest.java | 185 ++++++++++++++++++ 10 files changed, 228 insertions(+), 28 deletions(-) rename src/main/java/com/example/TradeHub/repository/{specified => }/user/UserRepository.java (81%) create mode 100644 src/test/java/com/example/TradeHub/WalletsServiceTest.java diff --git a/build.gradle b/build.gradle index 1be94f6..fa51c4e 100644 --- a/build.gradle +++ b/build.gradle @@ -35,9 +35,15 @@ dependencies { implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0' implementation 'org.springframework.boot:spring-boot-starter-web' //implementation 'ch.qos.logback:logback-classic:1.5.3' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testImplementation 'org.mockito:mockito-core:5.11.0' + implementation 'org.springframework.boot:spring-boot-starter-test:3.2.5' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframe work.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' } + +test { + useJUnitPlatform() +} diff --git a/src/main/java/com/example/TradeHub/TradeHubApplication.java b/src/main/java/com/example/TradeHub/TradeHubApplication.java index 6f00a43..ff050d1 100644 --- a/src/main/java/com/example/TradeHub/TradeHubApplication.java +++ b/src/main/java/com/example/TradeHub/TradeHubApplication.java @@ -11,8 +11,6 @@ public class TradeHubApplication { //TODO: Implement logging - //TODO: Create constant error messages - //TODO: Security Design //TODO: Fix errors in service with incorrect cryptocurrencies values diff --git a/src/main/java/com/example/TradeHub/domain/annotations/CryptoTransactionHistory/CryptoTransactionAspect.java b/src/main/java/com/example/TradeHub/domain/annotations/CryptoTransactionHistory/CryptoTransactionAspect.java index 3e9c844..83ca2ee 100644 --- a/src/main/java/com/example/TradeHub/domain/annotations/CryptoTransactionHistory/CryptoTransactionAspect.java +++ b/src/main/java/com/example/TradeHub/domain/annotations/CryptoTransactionHistory/CryptoTransactionAspect.java @@ -39,8 +39,6 @@ public void afterCryptoTransaction( return; } - - //Determine transaction type for(TransactionType transactionType : TransactionType.values()){ if(methodName.toUpperCase().contains(transactionType.name())){ diff --git a/src/main/java/com/example/TradeHub/repository/specified/user/UserRepository.java b/src/main/java/com/example/TradeHub/repository/user/UserRepository.java similarity index 81% rename from src/main/java/com/example/TradeHub/repository/specified/user/UserRepository.java rename to src/main/java/com/example/TradeHub/repository/user/UserRepository.java index 0a1f7b1..18e6baa 100644 --- a/src/main/java/com/example/TradeHub/repository/specified/user/UserRepository.java +++ b/src/main/java/com/example/TradeHub/repository/user/UserRepository.java @@ -1,10 +1,10 @@ -package com.example.TradeHub.repository.specified.user; - -import com.example.TradeHub.domain.user.User; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface UserRepository extends CrudRepository { - -} +package com.example.TradeHub.repository.user; + +import com.example.TradeHub.domain.user.User; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends CrudRepository { + +} diff --git a/src/main/java/com/example/TradeHub/service/CoinApiService.java b/src/main/java/com/example/TradeHub/service/CoinApiService.java index 5227f00..82d2601 100644 --- a/src/main/java/com/example/TradeHub/service/CoinApiService.java +++ b/src/main/java/com/example/TradeHub/service/CoinApiService.java @@ -11,9 +11,11 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; +import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import java.io.IOException; import java.math.BigDecimal; import java.net.URI; import java.time.LocalDateTime; @@ -76,7 +78,7 @@ private void setHeaders(){ public BigDecimal getCryptocurrencyPrice( String baseAsset, String quoteAsset - ) throws RuntimeException { + ) throws IOException { BigDecimal result = null; ResponseEntity response = restTemplate.exchange( @@ -93,7 +95,7 @@ public BigDecimal getCryptocurrencyPrice( result = exchange.rate(); } catch (Exception e) { - throw new RuntimeException("Failed to get the price of the cryptocurrency"); + throw new IOException("Failed to get the price of the cryptocurrency"); } return result; } diff --git a/src/main/java/com/example/TradeHub/service/CommerceService.java b/src/main/java/com/example/TradeHub/service/CommerceService.java index 375b388..43088ca 100644 --- a/src/main/java/com/example/TradeHub/service/CommerceService.java +++ b/src/main/java/com/example/TradeHub/service/CommerceService.java @@ -11,7 +11,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.math.BigDecimal; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.Random; @@ -35,7 +37,7 @@ public class CommerceService { */ public CompletedCryptoBuyRequest handleCryptoBuyRequest( CryptoUserRequest request - ){ + ) throws IOException { BigDecimal buyPrice = coinApiService.getCryptocurrencyPrice(request.baseAsset(), request.quoteAsset()); return buyCryptoRequestRepository.save( @@ -52,12 +54,11 @@ public CompletedCryptoBuyRequest handleCryptoBuyRequest( @CryptoTransaction public CryptoUserResponse confirmCryptoBuyRequest( Long requestId - ) { - + ) throws NoSuchElementException { //Allegedly received money from the user boolean userHaveMoney = new Random().nextBoolean(); CompletedCryptoBuyRequest request = buyCryptoRequestRepository.findById(requestId) - .orElseThrow(() -> new RuntimeException("Request not found")); + .orElseThrow(() -> new NoSuchElementException("Request not found")); if(userHaveMoney){ try{ @@ -74,7 +75,7 @@ public CryptoUserResponse confirmCryptoBuyRequest( @CryptoTransaction public CryptoUserResponse handleCryptoSellRequest( CryptoUserRequest request - ) { + ) throws NoSuchElementException { try { walletsService.decreaseUserBalance(request.userId(), request.baseAsset(), request.amount()); diff --git a/src/main/java/com/example/TradeHub/service/ConvertCryptoService.java b/src/main/java/com/example/TradeHub/service/ConvertCryptoService.java index 45ac0e4..5cc8b45 100644 --- a/src/main/java/com/example/TradeHub/service/ConvertCryptoService.java +++ b/src/main/java/com/example/TradeHub/service/ConvertCryptoService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.math.BigDecimal; import java.util.Optional; @@ -24,7 +25,7 @@ public class ConvertCryptoService { @CryptoTransaction public CryptoUserResponse handleConvertRequest( CryptoUserRequest request - ){ + ) throws IOException { //We receive a wallet from which the cryptocurrency will be debited CryptoWallet walletToGive = cryptoWalletRepository.findByUserAndCryptocurrency( request.userId(), diff --git a/src/main/java/com/example/TradeHub/service/TransferCryptoService.java b/src/main/java/com/example/TradeHub/service/TransferCryptoService.java index ebc8267..ba26e84 100644 --- a/src/main/java/com/example/TradeHub/service/TransferCryptoService.java +++ b/src/main/java/com/example/TradeHub/service/TransferCryptoService.java @@ -19,7 +19,6 @@ public CryptoTransferResponse handleTransferRequest( ){ try{ walletsService.decreaseUserBalance(request.senderId(),request.sentAsset(),request.sentAmount()); - walletsService.increaseUserBalance(request.recipientId(),request.sentAsset(),request.sentAmount()); return new CryptoTransferResponse(request, true); diff --git a/src/main/java/com/example/TradeHub/service/WalletsService.java b/src/main/java/com/example/TradeHub/service/WalletsService.java index d048101..fe3077c 100644 --- a/src/main/java/com/example/TradeHub/service/WalletsService.java +++ b/src/main/java/com/example/TradeHub/service/WalletsService.java @@ -1,6 +1,7 @@ package com.example.TradeHub.service; import com.example.TradeHub.domain.wallet.CryptoWallet; +import com.example.TradeHub.repository.user.UserRepository; import com.example.TradeHub.repository.wallet.CryptoWalletRepository; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; @@ -8,12 +9,14 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.util.NoSuchElementException; import java.util.Optional; @Service @RequiredArgsConstructor public class WalletsService { private final CryptoWalletRepository cryptoWalletRepository; + private final UserRepository userRepository; private final Logger logger = LoggerFactory.getLogger(WalletsService.class); private static final BigDecimal commissionCoefficient = new BigDecimal("1.03"); @@ -21,7 +24,11 @@ public void increaseUserBalance( Long userId, String asset, BigDecimal amount - ){ + ) throws NoSuchElementException{ + + userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("User not found")); + Optional wallet = cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset); if(wallet.isEmpty()){ @@ -44,14 +51,17 @@ public void decreaseUserBalance( Long userId, String asset, BigDecimal amount - ) throws RuntimeException{ + ) throws IllegalStateException{ + userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("User not found")); + CryptoWallet wallet = cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset) - .orElseThrow(() -> new RuntimeException("The user did not find the required crypto wallet")); + .orElseThrow(() -> new NoSuchElementException("The user did not find the required crypto wallet")); int compareResult = wallet.DecreaseBalance(amount); if (compareResult < 0) { - throw new RuntimeException("The sender does not have enough funds " + + throw new IllegalStateException("The sender does not have enough funds " + "to carry out the transfer of cryptocurrency"); } else { if(compareResult == 0){ diff --git a/src/test/java/com/example/TradeHub/WalletsServiceTest.java b/src/test/java/com/example/TradeHub/WalletsServiceTest.java new file mode 100644 index 0000000..b92d5bd --- /dev/null +++ b/src/test/java/com/example/TradeHub/WalletsServiceTest.java @@ -0,0 +1,185 @@ +package com.example.TradeHub; + +import com.example.TradeHub.domain.user.User; +import com.example.TradeHub.domain.wallet.CryptoWallet; +import com.example.TradeHub.repository.user.UserRepository; +import com.example.TradeHub.repository.wallet.CryptoWalletRepository; +import com.example.TradeHub.service.WalletsService; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; + +import java.math.BigDecimal; +import java.util.NoSuchElementException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + + +@SpringBootTest +public class WalletsServiceTest { + + @Mock + private CryptoWalletRepository cryptoWalletRepository; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private WalletsService walletsService; + + + @Test + public void testIncreaseUserBalance_NewWallet() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + BigDecimal amount = new BigDecimal("0.1"); + + when(cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset)) + .thenReturn(Optional.empty()); + + when(userRepository.findById(userId)) + .thenReturn(Optional.of(new User())); + + // Act + walletsService.increaseUserBalance(userId, asset, amount); + + // Assert + verify(cryptoWalletRepository).save(any()); + } + + @Test + public void testIncreaseUserBalance_ExistingWallet() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + BigDecimal amount = new BigDecimal("0.1"); + + CryptoWallet existingWallet = CryptoWallet.builder() + .userId(userId) + .baseAsset(asset) + .balance(new BigDecimal("1.0")) + .build(); + + when(userRepository.findById(userId)) + .thenReturn(Optional.of(new User())); + + when(cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset)) + .thenReturn(Optional.of(existingWallet)); + + // Act + walletsService.increaseUserBalance(userId, asset, amount); + + + // Assert + verify(cryptoWalletRepository).save(existingWallet); + } + + @Test + public void testIncreaseUserBalance_UserNotExist() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + BigDecimal amount = new BigDecimal("0.1"); + + when(userRepository.findById(userId)) + .thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NoSuchElementException.class, () -> { + walletsService.increaseUserBalance(userId, asset, amount); + }); + } + + @Test + public void testDecreaseUserBalance_WalletNotExist() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + BigDecimal amount = new BigDecimal("0.1"); + + when(cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset)) + .thenReturn(Optional.empty()); + + when(userRepository.findById(userId)) + .thenReturn(Optional.of(new User())); + + // Act && Assert + assertThrows(NoSuchElementException.class, () -> { + walletsService.decreaseUserBalance(userId, asset, amount); + }); + } + + @Test + public void testDecreaseUserBalance_ExistingWalletAndEnoughFunds() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + BigDecimal amount = new BigDecimal("0.1"); + + CryptoWallet existingWallet = CryptoWallet.builder() + .userId(userId) + .baseAsset(asset) + .balance(new BigDecimal("1.0")) + .build(); + + when(userRepository.findById(userId)) + .thenReturn(Optional.of(new User())); + + when(cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset)) + .thenReturn(Optional.of(existingWallet)); + + // Act + walletsService.decreaseUserBalance(userId, asset, amount); + + + // Assert + verify(cryptoWalletRepository).save(existingWallet); + } + + @Test + public void testDecreaseUserBalance_ExistingWalletAndNotEnoughFunds() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + + CryptoWallet existingWallet = CryptoWallet.builder() + .userId(userId) + .baseAsset(asset) + .balance(new BigDecimal("1.0")) + .build(); + + BigDecimal amount = existingWallet.getBalance().add(new BigDecimal(1L)); + + when(userRepository.findById(userId)) + .thenReturn(Optional.of(new User())); + + when(cryptoWalletRepository.findByUserAndCryptocurrency(userId, asset)) + .thenReturn(Optional.of(existingWallet)); + + // Act & Assert + assertThrows(IllegalStateException.class, () -> { + walletsService.decreaseUserBalance(userId, asset, amount); + }); + } + + @Test + public void testDecreaseUserBalance_UserNotExist() { + // Arrange + Long userId = 1L; + String asset = "BTC"; + BigDecimal amount = new BigDecimal("0.1"); + + when(userRepository.findById(userId)) + .thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NoSuchElementException.class, () -> { + walletsService.decreaseUserBalance(userId, asset, amount); + }); + } +} \ No newline at end of file