diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImpl.java b/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImpl.java index 41a079700..7573b185b 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImpl.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImpl.java @@ -8,6 +8,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; @@ -84,6 +87,8 @@ public class CardanoConstructionServiceImpl implements CardanoConstructionServic private final ProtocolParamService protocolParamService; private final RestTemplate restTemplate; + private ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); + @Value("${cardano.rosetta.NODE_SUBMIT_API_PORT}") private int nodeSubmitApiPort; @Value("${cardano.rosetta.CARDANO_NODE_SUBMIT_HOST}") @@ -227,31 +232,18 @@ public String buildTransaction(String unsignedTransaction, List sign String transactionMetadata) { log.info("[buildTransaction] About to signed a transaction with {} signatures", signaturesList.size()); - TransactionWitnessSet witnesses = getWitnessesForTransaction(signaturesList); - - log.info("[buildTransaction] Instantiating transaction body from unsigned transaction bytes"); - DataItem[] dataItems; - try { - dataItems = com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.deserialize( - HexUtil.decodeHexString(unsignedTransaction)); - } catch (Exception e) { - throw ExceptionFactory.cantCreateSignTransaction(); - } + CompletableFuture transactionBodyFuture = + CompletableFuture.supplyAsync(() -> deserializeTransactionBody(unsignedTransaction), executorService); + CompletableFuture witnessesFuture = + CompletableFuture.supplyAsync(() -> getWitnessesForTransaction(signaturesList), executorService); + CompletableFuture auxiliaryDataFuture = + CompletableFuture.supplyAsync(() -> deserializeAuxiliaryData(transactionMetadata), executorService); try { - TransactionBody transactionBody = TransactionBody.deserialize( - (co.nstant.in.cbor.model.Map) dataItems[0]); log.info( "[buildTransaction] Creating transaction using transaction body and extracted witnesses"); - AuxiliaryData auxiliaryData = null; - if (!ObjectUtils.isEmpty(transactionMetadata)) { - log.info("[buildTransaction] Adding transaction metadata"); - Array array = (Array) CborSerializationUtil.deserialize( - HexUtil.decodeHexString(transactionMetadata)); - auxiliaryData = AuxiliaryData.deserialize( - (co.nstant.in.cbor.model.Map) array.getDataItems().getFirst()); - } - Transaction transaction = Transaction.builder().auxiliaryData(auxiliaryData) - .witnessSet(witnesses).build(); + Transaction transaction = Transaction.builder().auxiliaryData(auxiliaryDataFuture.join()) + .witnessSet(witnessesFuture.join()).build(); + TransactionBody transactionBody = transactionBodyFuture.join(); transaction.setBody(transactionBody); Array array = (Array) CborSerializationUtil.deserialize(transaction.serialize()); if (transactionBody.getTtl() == 0) { @@ -270,10 +262,6 @@ public String buildTransaction(String unsignedTransaction, List sign } return HexUtil.encodeHexString( com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.serialize(array)); - } catch (CborDeserializationException e) { - log.error("{} [buildTransaction] CborDeserializationException while building transaction", - e.getMessage()); - throw ExceptionFactory.generalDeserializationError(e.getMessage()); } catch (CborSerializationException e) { log.error("{} [buildTransaction] CborSerializationException while building transaction", e.getMessage()); @@ -288,11 +276,13 @@ public TransactionWitnessSet getWitnessesForTransaction(List signatu ArrayList bootstrapWitnesses = new ArrayList<>(); log.info("[getWitnessesForTransaction] Extracting witnesses from signatures"); signaturesList.forEach(signature -> { - VerificationKey vKey = new VerificationKey(); - vKey.setCborHex(ObjectUtils.isEmpty(signature) ? null : signature.publicKey()); + if (ObjectUtils.isEmpty(signature)) { + return; + } + + VerificationKey vKey = new VerificationKey(signature.publicKey()); EraAddressType eraAddressType = CardanoAddressUtils.getEraAddressType(signature.address()); - if (!ObjectUtils.isEmpty(signature)) { - if (!ObjectUtils.isEmpty(signature.address()) && eraAddressType == EraAddressType.BYRON) { + if (eraAddressType == EraAddressType.BYRON) { // byron case ValidateParseUtil.validateChainCode(signature.chainCode()); ByronAddress byronAddress = new ByronAddress(signature.address()); @@ -310,7 +300,6 @@ public TransactionWitnessSet getWitnessesForTransaction(List signatu vKeyWitnesses.add(new VkeyWitness(HexUtil.decodeHexString(vKey.getCborHex()), HexUtil.decodeHexString(signature.signature()))); } - } }); log.info("[getWitnessesForTransaction] {} witnesses were extracted to sign transaction", vKeyWitnesses.size()); @@ -631,4 +620,30 @@ private Signatures extractSignaturesFromAddress(String address) { } throw ExceptionFactory.invalidAddressError(address); } + + private TransactionBody deserializeTransactionBody(String unsignedTransaction) { + log.info("[buildTransaction] Instantiating transaction body from unsigned transaction bytes"); + try { + DataItem[] dataItems = com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.deserialize( + HexUtil.decodeHexString(unsignedTransaction)); + return TransactionBody.deserialize((co.nstant.in.cbor.model.Map) dataItems[0]); + } catch (Exception e) { + log.error("[buildTransaction] Error deserializing unsigned transaction: {}", e.getMessage()); + throw ExceptionFactory.cantCreateSignTransaction(); + } + } + + private AuxiliaryData deserializeAuxiliaryData(String transactionMetadata) { + if (ObjectUtils.isEmpty(transactionMetadata)) { + return null; + } + try { + log.info("[buildTransaction] Adding transaction metadata"); + Array array = (Array) CborSerializationUtil.deserialize(HexUtil.decodeHexString(transactionMetadata)); + return AuxiliaryData.deserialize((co.nstant.in.cbor.model.Map) array.getDataItems().getFirst()); + } catch (Exception e) { + log.error("[buildTransaction] CborDeserializationException while deserializing transactionMetadata: {}", e.getMessage()); + throw ExceptionFactory.generalDeserializationError(e.getMessage()); + } + } } diff --git a/api/src/main/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandler.java b/api/src/main/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandler.java index a78cf321d..e39155053 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandler.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package org.cardanofoundation.rosetta.common.exception; import java.util.List; +import java.util.concurrent.CompletionException; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -35,6 +36,16 @@ public ResponseEntity handleApiException(ApiException apiException) { return new ResponseEntity<>(apiException.getError(), HttpStatus.INTERNAL_SERVER_ERROR); } + @ExceptionHandler(CompletionException.class) + public ResponseEntity handleCompletionException(CompletionException exception, HttpServletRequest request) { + Throwable cause = exception.getCause(); + if (cause instanceof ApiException apiException) { + log.error("An API exception has raised", apiException); + return new ResponseEntity<>(apiException.getError(), HttpStatus.INTERNAL_SERVER_ERROR); + } + return handleGlobalException(exception, request); + } + @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValidException( MethodArgumentNotValidException methodArgumentNotValidException, HttpServletRequest request) { diff --git a/api/src/test/java/org/cardanofoundation/rosetta/EntityGenerator.java b/api/src/test/java/org/cardanofoundation/rosetta/EntityGenerator.java index d3125ffda..e5c3aad0d 100644 --- a/api/src/test/java/org/cardanofoundation/rosetta/EntityGenerator.java +++ b/api/src/test/java/org/cardanofoundation/rosetta/EntityGenerator.java @@ -53,6 +53,8 @@ import org.openapitools.client.model.TransactionIdentifierResponse; import org.openapitools.client.model.Version; +import org.cardanofoundation.rosetta.common.model.cardano.crypto.Signatures; + import static org.cardanofoundation.rosetta.common.util.Constants.*; import static org.openapitools.client.model.CurveType.EDWARDS25519; @@ -343,4 +345,11 @@ public static Operation givenOperation() { .build()) .build(); } + + public static Signatures givenSignatures() { + return new Signatures("dc2a1948bfa9411b37e8d280b04c48a85af5588bcf509c0fca798f7b462ebca92d6733dacc1f1c6c1463623c085401be07ea422ad4f1c543375e7d3d2393aa0b", + "73fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d", + "dd75e154da417becec55cdd249327454138f082110297d5e87ab25e15fad150f", + "Ae2tdPwUPEZC6WJfVQxTNN2tWw4skGrN6zRVukvxJmTFy1nYkVGQBuURU3L"); + } } diff --git a/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImplTest.java b/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImplTest.java index d0c34c087..969f6c8ac 100644 --- a/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImplTest.java +++ b/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImplTest.java @@ -4,6 +4,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import jakarta.validation.constraints.NotNull; @@ -15,6 +17,8 @@ import org.springframework.web.client.RestTemplate; import co.nstant.in.cbor.CborException; import com.bloxbean.cardano.client.util.HexUtil; +import com.bloxbean.cardano.yaci.core.exception.CborRuntimeException; +import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; @@ -23,6 +27,7 @@ import org.openapitools.client.model.Operation; import org.openapitools.client.model.PublicKey; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -32,6 +37,7 @@ import org.cardanofoundation.rosetta.common.exception.ApiException; import org.cardanofoundation.rosetta.common.exception.Error; import org.cardanofoundation.rosetta.common.mapper.CborArrayToTransactionData; +import org.cardanofoundation.rosetta.common.model.cardano.crypto.Signatures; import org.cardanofoundation.rosetta.common.model.cardano.transaction.TransactionParsed; import org.cardanofoundation.rosetta.common.util.CardanoAddressUtils; import org.cardanofoundation.rosetta.common.util.Constants; @@ -39,6 +45,7 @@ import static org.cardanofoundation.rosetta.EntityGenerator.givenOperation; import static org.cardanofoundation.rosetta.EntityGenerator.givenPublicKey; +import static org.cardanofoundation.rosetta.EntityGenerator.givenSignatures; import static org.cardanofoundation.rosetta.api.construction.enumeration.AddressType.BASE; import static org.cardanofoundation.rosetta.api.construction.enumeration.AddressType.REWARD; import static org.cardanofoundation.rosetta.common.enumeration.NetworkEnum.PREPROD; @@ -59,17 +66,34 @@ @ExtendWith(MockitoExtension.class) class CardanoConstructionServiceImplTest { - @Mock - private RestTemplate restTemplate; private final static String TRANSACTION_SIGNED = "8279025a3833613430303831383235383230326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663031303138323832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666238323139323731306131353831636230643037643435666539353134663830323133663430323065356136313234313435386265363236383431636465373137636233386137613334393437373536393634366634333666363936653139303930363530346137353631366534333732373537613534366636623635366536313761366631393161306134373665373537343633366636393665313932373130383235383164363162623430663161363437626338386331626436623733386462386562363633353764393236343734656135666664366261613736633966623139396334303032313939633430303330306131303038313832353832303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303035383430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303066356636a16a6f7065726174696f6e7381a7746f7065726174696f6e5f6964656e746966696572a265696e646578006d6e6574776f726b5f696e64657800647479706565696e707574667374617475736773756363657373676163636f756e74a16761646472657373783a616464723176786135707564786737376733736461646465636d773874766336686d796e79776e34396c6c747434666d766e3763706e6b63707866616d6f756e74a26576616c7565662d39303030306863757272656e6379a26673796d626f6c6341444168646563696d616c73066b636f696e5f6368616e6765a26f636f696e5f6964656e746966696572a16a6964656e7469666965727842326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663a316b636f696e5f616374696f6e6a636f696e5f7370656e74686d65746164617461a16b746f6b656e42756e646c6581a268706f6c69637949647838623064303764343566653935313466383032313366343032306535613631323431343538626536323638343163646537313763623338613766746f6b656e7383a26576616c756564323331306863757272656e6379a26673796d626f6c7234373735363936343666343336663639366568646563696d616c7300a26576616c756564363636366863757272656e6379a26673796d626f6c7820346137353631366534333732373537613534366636623635366536313761366668646563696d616c7300a26576616c75656531303030306863757272656e6379a26673796d626f6c6e366537353734363336663639366568646563696d616c7300"; private final static String TRANSACTION_NOT_SIGNED = "82790132613530303831383235383230326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663031303138323832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666230313832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666230343032316130353762636566623033313930336538303438313832303138323030353831636262343066316136343762633838633162643662373338646238656236363335376439323634373465613566666436626161373663396662a16a6f7065726174696f6e7382a6746f7065726174696f6e5f6964656e746966696572a265696e646578006d6e6574776f726b5f696e64657800647479706565696e707574667374617475736773756363657373676163636f756e74a16761646472657373783a616464723176786135707564786737376733736461646465636d773874766336686d796e79776e34396c6c747434666d766e3763706e6b63707866616d6f756e74a26576616c7565692d39303030303030306863757272656e6379a26673796d626f6c6341444168646563696d616c73066b636f696e5f6368616e6765a26f636f696e5f6964656e746966696572a16a6964656e7469666965727842326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663a316b636f696e5f616374696f6e6a636f696e5f7370656e74a5746f7065726174696f6e5f6964656e746966696572a165696e646578036474797065767374616b654b65794465726567697374726174696f6e667374617475736773756363657373676163636f756e74a16761646472657373783b7374616b653175387a666e6b687034673676686e6565746d763271656e3766356e64726b6c716a7138653973326e636b3968333063667a36716d70686d65746164617461a2727374616b696e675f63726564656e7469616ca2696865785f62797465737840314234303044363041414633344541463644434241423942424134363030314132333439373838364346313130363646373834363933334433304535414433466a63757276655f747970656c6564776172647332353531396c726566756e64416d6f756e74a26576616c7565682d323030303030306863757272656e6379a26673796d626f6c6341444168646563696d616c7306"; + private final static String COMBINE_UNSIGNED_TRANSACTION = "a400818258202f23fd8cca835af21f3ac375bac601f97ead75f2e79143bdf71fe2c4be043e8f01018282581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb19271082581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb199c4002199c40031903e8"; + private final static String COMBINE_SIGNED_TRANSACTION = "84a400818258202f23fd8cca835af21f3ac375bac601f97ead75f2e79143bdf71fe2c4be043e8f01018282581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb19271082581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb199c4002199c40031903e8a1028184582073fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d5840dc2a1948bfa9411b37e8d280b04c48a85af5588bcf509c0fca798f7b462ebca92d6733dacc1f1c6c1463623c085401be07ea422ad4f1c543375e7d3d2393aa0b5820dd75e154da417becec55cdd249327454138f082110297d5e87ab25e15fad150f41a0f5f6"; + + @Mock + private RestTemplate restTemplate; @InjectMocks private CardanoConstructionServiceImpl cardanoService; + private MockedStatic completableFutureMock; + @BeforeEach void setup() { cardanoService = new CardanoConstructionServiceImpl(null, null,restTemplate); + completableFutureMock = Mockito.mockStatic(CompletableFuture.class, invocation -> { + if (invocation.getMethod().getName().equals("supplyAsync")) { + Supplier supplier = invocation.getArgument(0); + return CompletableFuture.completedFuture(supplier.get()); + } + return invocation.callRealMethod(); + }); + } + + @AfterEach + void tearDown() { + completableFutureMock.close(); } @Test @@ -402,6 +426,53 @@ void getHdPublicKeyFromRosettaKeyTest() { assertEquals("Invalid public key length", exception.getMessage()); } + @Test + void buildTransaction() { + List signatures = Collections.singletonList(givenSignatures()); + String result = cardanoService.buildTransaction(COMBINE_UNSIGNED_TRANSACTION, signatures, ""); + + assertEquals(COMBINE_SIGNED_TRANSACTION, result); + } + + @Test + void buildTransaction_whenCannotDeserializeTransactionBody_thenThrowException() { + List signatures = Collections.singletonList(givenSignatures()); + + try (MockedStatic mocked = Mockito.mockStatic(CborSerializationUtil.class)) { + mocked.when(() -> CborSerializationUtil.deserialize(any(byte[].class))) + .thenThrow(new CborRuntimeException("CborException")); + + ApiException result = assertThrows(ApiException.class, + () -> cardanoService.buildTransaction(COMBINE_UNSIGNED_TRANSACTION, signatures, "")); + + assertEquals(RosettaErrorType.CANT_CREATE_SIGN_TRANSACTION.getMessage(), result.getError().getMessage()); + assertEquals(RosettaErrorType.CANT_CREATE_SIGN_TRANSACTION.getCode(), result.getError().getCode()); + assertFalse(result.getError().isRetriable()); + mocked.verify(() -> CborSerializationUtil.deserialize(any(byte[].class)), times(1)); + } + } + + @Test + void buildTransaction_whenCannotDeserializeAuxiliaryData_thenThrowException() { + List signatures = Collections.singletonList(givenSignatures()); + + try (MockedStatic mocked + = Mockito.mockStatic(com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.class)) { + mocked.when(() -> com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.deserialize(any())) + .thenThrow(new com.bloxbean.cardano.client.exception.CborRuntimeException("CborException")); + + ApiException result = assertThrows(ApiException.class, + () -> cardanoService.buildTransaction(COMBINE_UNSIGNED_TRANSACTION, signatures, COMBINE_UNSIGNED_TRANSACTION)); + + assertEquals(RosettaErrorType.DESERIALIZATION_ERROR.getMessage(), result.getError().getMessage()); + assertEquals(RosettaErrorType.DESERIALIZATION_ERROR.getCode(), result.getError().getCode()); + assertEquals("CborException", result.getError().getDetails().getMessage()); + assertFalse(result.getError().isRetriable()); + mocked.verify(() -> com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.deserialize(any()), + times(1)); + } + } + @NotNull private HttpHeaders given() { ReflectionTestUtils.setField(cardanoService, "nodeSubmitApiPort", 8080); diff --git a/api/src/test/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandlerTest.java b/api/src/test/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandlerTest.java index 73278cd24..e48c84766 100644 --- a/api/src/test/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandlerTest.java +++ b/api/src/test/java/org/cardanofoundation/rosetta/common/exception/GlobalExceptionHandlerTest.java @@ -1,6 +1,7 @@ package org.cardanofoundation.rosetta.common.exception; import java.util.Collections; +import java.util.concurrent.CompletionException; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpStatus; @@ -18,6 +19,7 @@ import org.cardanofoundation.rosetta.common.util.RosettaConstants.RosettaErrorType; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -32,6 +34,8 @@ class GlobalExceptionHandlerTest { private static final String API_MESSAGE = "API Exception"; private static final String GLOBAL_EXCEPTION_MESSAGE = "An error occurred for request " + TEST_ID + ": " + GLOBAL_MESSAGE; + private static final String COMPLETION_GLOBAL_EXCEPTION_MESSAGE = + "An error occurred for request " + TEST_ID + ": " + "java.lang.Exception: " + GLOBAL_MESSAGE; private static final String API_EXCEPTION_MESSAGE = "An error occurred for request " + TEST_ID + ": " + API_MESSAGE; private static final String ARGUMENT_NOT_VALID_EXCEPTION_MESSAGE = @@ -60,6 +64,7 @@ void handleGlobalException_shouldReturnInternalServerError() { ResponseEntity result = underTest.handleGlobalException(globalException, request); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + assertNotNull(result.getBody()); assertEquals(GLOBAL_EXCEPTION_MESSAGE, result.getBody().getDetails().getMessage()); assertEquals(CARDANO_ERROR_CODE, result.getBody().getCode()); } @@ -71,11 +76,43 @@ void handleAPIException_shouldReturnInternalServerError() { ResponseEntity result = underTest.handleApiException(apiException); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + assertNotNull(result.getBody()); assertEquals(apiException.getError().getDetails().getMessage(), result.getBody().getDetails().getMessage()); assertEquals(apiException.getError().getCode(), result.getBody().getCode()); } + @Test + void handleCompletionException_whenAPIExceptionWrapped_shouldReturnInternalServerError() { + ApiException apiException = createApiException(); + CompletionException completionException = new CompletionException(apiException); + + ResponseEntity result = underTest.handleCompletionException(completionException, + request); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + assertNotNull(result.getBody()); + assertEquals(apiException.getError().getDetails().getMessage(), + result.getBody().getDetails().getMessage()); + assertEquals(apiException.getError().getCode(), result.getBody().getCode()); + } + + @Test + void handleCompletionException_whenGlobalExceptionWrapped_shouldReturnInternalServerError() { + Exception globalException = new Exception(GLOBAL_MESSAGE); + CompletionException completionException = new CompletionException(globalException); + + when(request.getRequestId()).thenReturn(TEST_ID); + + ResponseEntity result = underTest.handleCompletionException(completionException, + request); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + assertNotNull(result.getBody()); + assertEquals(COMPLETION_GLOBAL_EXCEPTION_MESSAGE, result.getBody().getDetails().getMessage()); + assertEquals(CARDANO_ERROR_CODE, result.getBody().getCode()); + } + @Test void handleMethodArgumentNotValidException_shouldReturnInternalServerError() { when(methodArgumentNotValidException.getBindingResult()).thenReturn(bindingResult); @@ -88,6 +125,7 @@ void handleMethodArgumentNotValidException_shouldReturnInternalServerError() { methodArgumentNotValidException, request); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + assertNotNull(result.getBody()); assertEquals(ARGUMENT_NOT_VALID_EXCEPTION_MESSAGE, result.getBody().getDetails().getMessage()); assertEquals(CARDANO_ERROR_CODE, result.getBody().getCode()); }