From 3070869aef03bb40018f42b9a1de17d1641cfd99 Mon Sep 17 00:00:00 2001 From: pedrolopes9-7 Date: Thu, 29 Aug 2024 22:51:41 -0300 Subject: [PATCH 1/5] adding feign clients and initial service structure --- .../PaymentMethodClient.java | 15 ++++++++ .../Purchase.java | 28 +++++++++++++++ .../PurchaseService.java | 28 +++++++++++++++ .../ReportClient.java | 13 +++++++ .../ReportRequest.java | 34 +++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java new file mode 100644 index 000000000000..1d613201f421 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java @@ -0,0 +1,15 @@ +package com.baeldung.cloud.openfeign.completablefuturefeignclient; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@FeignClient(name = "paymentClient", url = "http://payments-api.com") +public interface PaymentMethodClient { + + @RequestMapping(method = RequestMethod.GET, value = "/payments/methods") + List getAvailablePaymentMethods(@RequestParam(name = "site_id") String siteId); +} diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java new file mode 100644 index 000000000000..c6c22ebb125e --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java @@ -0,0 +1,28 @@ +package com.baeldung.cloud.openfeign.completablefuturefeignclient; + +public class Purchase { + + private String siteId; + private long orderId; + + public Purchase(String siteId, long orderId) { + this.siteId = siteId; + this.orderId = orderId; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public long getOrderId() { + return orderId; + } + + public void setOrderId(long orderId) { + this.orderId = orderId; + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java new file mode 100644 index 000000000000..68496396496d --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java @@ -0,0 +1,28 @@ +package com.baeldung.cloud.openfeign.completablefuturefeignclient; + +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Service +public class PurchaseService { + + private final PaymentMethodClient paymentMethodClient; + private final ReportClient reportClient; + + public PurchaseService(PaymentMethodClient paymentMethodClient, ReportClient reportClient) { + this.paymentMethodClient = paymentMethodClient; + this.reportClient = reportClient; + } + + public void executePurchase(Purchase purchase) { + + ReportRequest report = new ReportRequest("Creating purchase for order", purchase.getOrderId(), purchase.getSiteId()); + + CompletableFuture.allOf( + CompletableFuture.supplyAsync(() -> paymentMethodClient.getAvailablePaymentMethods(purchase.getSiteId())), + CompletableFuture.runAsync(() -> reportClient.sendReport(report)) + ); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java new file mode 100644 index 000000000000..87da51ea6428 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java @@ -0,0 +1,13 @@ +package com.baeldung.cloud.openfeign.completablefuturefeignclient; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@FeignClient(name = "reportClient", url = "http://reports-api.com") +public interface ReportClient { + + @RequestMapping(method = RequestMethod.POST, value = "/reports") + void sendReport(@RequestBody ReportRequest reportRequest); +} diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java new file mode 100644 index 000000000000..db82116c7d2a --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java @@ -0,0 +1,34 @@ +package com.baeldung.cloud.openfeign.completablefuturefeignclient; + +public class ReportRequest { + + private String content; + private long orderId; + private String siteId; + + public ReportRequest(String content, long orderId, String siteId) { + this.content = content; + this.orderId = orderId; + this.siteId = siteId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public long getOrderId() { + return orderId; + } + + public void setOrderId(long orderId) { + this.orderId = orderId; + } + + public String getSiteId() { + return siteId; + } +} From 93da6f8e523ee98d44b6848747f712f31c68b7ce Mon Sep 17 00:00:00 2001 From: pedrolopes9-7 Date: Mon, 2 Sep 2024 22:54:25 -0300 Subject: [PATCH 2/5] adding tests and wrapping up futures service --- .../spring-cloud-openfeign-2/pom.xml | 12 ++++ .../PaymentMethodClient.java | 6 +- .../Purchase.java | 16 +---- .../PurchaseService.java | 30 +++++++-- .../ReportClient.java | 4 +- .../ReportRequest.java | 34 ---------- .../PurchaseServiceIntegrationTest.java | 62 +++++++++++++++++++ 7 files changed, 104 insertions(+), 60 deletions(-) delete mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/pom.xml b/spring-cloud-modules/spring-cloud-openfeign-2/pom.xml index ebfbc02755d8..041d3fea787a 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/pom.xml +++ b/spring-cloud-modules/spring-cloud-openfeign-2/pom.xml @@ -7,6 +7,18 @@ spring-cloud-openfeign-2 spring-cloud-openfeign-2 OpenFeign project for Spring Boot + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + + com.baeldung.spring.cloud diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java index 1d613201f421..8de8861630b7 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java @@ -7,9 +7,9 @@ import java.util.List; -@FeignClient(name = "paymentClient", url = "http://payments-api.com") +@FeignClient(name = "paymentClient", url = "http://localhost:8083") public interface PaymentMethodClient { - @RequestMapping(method = RequestMethod.GET, value = "/payments/methods") - List getAvailablePaymentMethods(@RequestParam(name = "site_id") String siteId); + @RequestMapping(method = RequestMethod.POST, value = "/purchase") + String processPurchase(@RequestParam(name = "site_id") String siteId); } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java index c6c22ebb125e..d7b1d1c607e7 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java @@ -3,26 +3,12 @@ public class Purchase { private String siteId; - private long orderId; - public Purchase(String siteId, long orderId) { + public Purchase(String siteId) { this.siteId = siteId; - this.orderId = orderId; } public String getSiteId() { return siteId; } - - public void setSiteId(String siteId) { - this.siteId = siteId; - } - - public long getOrderId() { - return orderId; - } - - public void setOrderId(long orderId) { - this.orderId = orderId; - } } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java index 68496396496d..23bddc440fce 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java @@ -1,9 +1,14 @@ package com.baeldung.cloud.openfeign.completablefuturefeignclient; +import feign.FeignException; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static java.lang.String.format; @Service public class PurchaseService { @@ -16,13 +21,26 @@ public PurchaseService(PaymentMethodClient paymentMethodClient, ReportClient rep this.reportClient = reportClient; } - public void executePurchase(Purchase purchase) { + public String executePurchase(Purchase purchase) throws ExecutionException, InterruptedException { + CompletableFuture paymentMethodsFuture = CompletableFuture.supplyAsync(() -> paymentMethodClient.processPurchase(purchase.getSiteId())) + .handle((res, ex) -> { + if (ex.getCause() instanceof FeignException) { + int status = ((FeignException) ex.getCause()).status(); + + if (status == 404) { + return null; + } + } + return res; + }) + .orTimeout(2, TimeUnit.SECONDS); + + CompletableFuture reportFuture = CompletableFuture.runAsync(() -> reportClient.sendReport("Purchase Order Report")) + .orTimeout(1, TimeUnit.SECONDS); - ReportRequest report = new ReportRequest("Creating purchase for order", purchase.getOrderId(), purchase.getSiteId()); + CompletableFuture.allOf(paymentMethodsFuture, reportFuture) + .join(); - CompletableFuture.allOf( - CompletableFuture.supplyAsync(() -> paymentMethodClient.getAvailablePaymentMethods(purchase.getSiteId())), - CompletableFuture.runAsync(() -> reportClient.sendReport(report)) - ); + return paymentMethodsFuture.get(); } } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java index 87da51ea6428..39bab720b539 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java @@ -5,9 +5,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -@FeignClient(name = "reportClient", url = "http://reports-api.com") +@FeignClient(name = "reportClient", url = "http://localhost:8083") public interface ReportClient { @RequestMapping(method = RequestMethod.POST, value = "/reports") - void sendReport(@RequestBody ReportRequest reportRequest); + void sendReport(@RequestBody String reportRequest); } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java deleted file mode 100644 index db82116c7d2a..000000000000 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.baeldung.cloud.openfeign.completablefuturefeignclient; - -public class ReportRequest { - - private String content; - private long orderId; - private String siteId; - - public ReportRequest(String content, long orderId, String siteId) { - this.content = content; - this.orderId = orderId; - this.siteId = siteId; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public long getOrderId() { - return orderId; - } - - public void setOrderId(long orderId) { - this.orderId = orderId; - } - - public String getSiteId() { - return siteId; - } -} diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java new file mode 100644 index 000000000000..1f37bedfdc9b --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java @@ -0,0 +1,62 @@ +package com.baeldung.cloud.openfeign.completablefuturefeignclient; + +import com.baeldung.cloud.openfeign.ExampleApplication; +import com.github.tomakehurst.wiremock.WireMockServer; +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; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.concurrent.ExecutionException; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = ExampleApplication.class) +class PurchaseServiceIntegrationTest { + + @Autowired + private PurchaseService purchaseService; + + @Autowired + private PaymentMethodClient paymentMethodClient; + + @Autowired + private ReportClient reportClient; + + private WireMockServer wireMockServer; + + @BeforeEach + public void startWireMockServer() { + wireMockServer = new WireMockServer(8083); + configureFor("localhost", 8083); + wireMockServer.start(); + } + + @AfterEach + public void stopWireMockServer() { + wireMockServer.stop(); + } + + @Test + void executePurchase() throws ExecutionException, InterruptedException { + stubFor(post(urlEqualTo("/purchase?site_id=BR")) + .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + + stubFor(post(urlEqualTo("/reports")) + .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); + + Purchase purchase = new Purchase("BR"); + + String result = purchaseService.executePurchase(purchase); + + assertNull(result); + } +} \ No newline at end of file From a82e5451eea16b679e3c26657d31ef8f966037c2 Mon Sep 17 00:00:00 2001 From: pedrolopes9-7 Date: Thu, 5 Sep 2024 22:42:16 -0300 Subject: [PATCH 3/5] adding tests --- .../PaymentMethodClient.java | 2 - .../Purchase.java | 2 +- .../PurchaseService.java | 37 +++++++------ .../PurchaseServiceIntegrationTest.java | 55 ++++++++++++++++--- .../application-integration_test.properties | 4 ++ 5 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java index 8de8861630b7..af2d39bb312a 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java @@ -5,8 +5,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -import java.util.List; - @FeignClient(name = "paymentClient", url = "http://localhost:8083") public interface PaymentMethodClient { diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java index d7b1d1c607e7..99f2ec151cd4 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java @@ -2,7 +2,7 @@ public class Purchase { - private String siteId; + String siteId; public Purchase(String siteId) { this.siteId = siteId; diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java index 23bddc440fce..dece2920caab 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java @@ -1,14 +1,13 @@ package com.baeldung.cloud.openfeign.completablefuturefeignclient; import feign.FeignException; +import feign.RetryableException; import org.springframework.stereotype.Service; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; - -import static java.lang.String.format; +import java.util.concurrent.TimeoutException; @Service public class PurchaseService { @@ -23,24 +22,28 @@ public PurchaseService(PaymentMethodClient paymentMethodClient, ReportClient rep public String executePurchase(Purchase purchase) throws ExecutionException, InterruptedException { CompletableFuture paymentMethodsFuture = CompletableFuture.supplyAsync(() -> paymentMethodClient.processPurchase(purchase.getSiteId())) - .handle((res, ex) -> { - if (ex.getCause() instanceof FeignException) { - int status = ((FeignException) ex.getCause()).status(); + .orTimeout(400, TimeUnit.MILLISECONDS) + .exceptionally(ex -> { + if (ex.getCause() instanceof FeignException && ((FeignException) ex.getCause()).status() == 404) { + return "account_money"; + } - if (status == 404) { - return null; - } + if (ex.getCause() instanceof RetryableException) { + // handle REST timeout + throw new RuntimeException("REST call network timeout!"); } - return res; - }) - .orTimeout(2, TimeUnit.SECONDS); - CompletableFuture reportFuture = CompletableFuture.runAsync(() -> reportClient.sendReport("Purchase Order Report")) - .orTimeout(1, TimeUnit.SECONDS); + if (ex instanceof TimeoutException) { + // handle thread timeout + throw new RuntimeException("Thread timeout!", ex); + } + + throw new RuntimeException("Unrecoverable error!", ex); + }); - CompletableFuture.allOf(paymentMethodsFuture, reportFuture) - .join(); + CompletableFuture.runAsync(() -> reportClient.sendReport("Purchase Order Report")) + .orTimeout(1, TimeUnit.SECONDS); - return paymentMethodsFuture.get(); + return String.format("Purchase executed with payment method %s", paymentMethodsFuture.get()); } } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java index 1f37bedfdc9b..55e57e05417b 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java @@ -8,18 +8,21 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.openfeign.FeignClientProperties; import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; @ExtendWith(SpringExtension.class) @SpringBootTest(classes = ExampleApplication.class) +@TestPropertySource(locations = "classpath:application-integration_test.properties") class PurchaseServiceIntegrationTest { @Autowired @@ -33,11 +36,18 @@ class PurchaseServiceIntegrationTest { private WireMockServer wireMockServer; + private Purchase purchase; + @BeforeEach public void startWireMockServer() { wireMockServer = new WireMockServer(8083); configureFor("localhost", 8083); wireMockServer.start(); + + stubFor(post(urlEqualTo("/reports")) + .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); + + purchase = new Purchase("BR"); } @AfterEach @@ -46,17 +56,46 @@ public void stopWireMockServer() { } @Test - void executePurchase() throws ExecutionException, InterruptedException { + void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() + throws ExecutionException, InterruptedException { stubFor(post(urlEqualTo("/purchase?site_id=BR")) - .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + .willReturn(aResponse().withStatus(HttpStatus.OK.value()).withBody("credit_card"))); - stubFor(post(urlEqualTo("/reports")) - .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); + String result = purchaseService.executePurchase(purchase); - Purchase purchase = new Purchase("BR"); + assertNotNull(result); + assertEquals("Purchase executed with payment method credit_card", result); + } + + @Test + void givenRestCalls_whenPurchaseReturns404_thenReturnDefault() + throws ExecutionException, InterruptedException { + stubFor(post(urlEqualTo("/purchase?site_id=BR")) + .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); String result = purchaseService.executePurchase(purchase); - assertNull(result); + assertNotNull(result); + assertEquals("Purchase executed with payment method account_money", result); + } + + @Test + void givenRestCalls_whenPurchaseCompletableFutureTimeout_thenReturnDefault() { + stubFor(post(urlEqualTo("/purchase?site_id=BR")) + .willReturn(aResponse().withFixedDelay(450))); + + Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase(purchase)); + + assertEquals("java.lang.RuntimeException: Thread timeout!", error.getMessage()); } + +// @Test +// void givenRestCalls_whenPurchaseRequestWebTimeout_thenReturnDefault() { +// stubFor(post(urlEqualTo("/purchase?site_id=BR")) +// .willReturn(aResponse().withFixedDelay(250))); +// +// Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase(purchase)); +// +// assertEquals("REST call network timeout!", error.getMessage()); +// } } \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties new file mode 100644 index 000000000000..41cd030c8b9f --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties @@ -0,0 +1,4 @@ +feign.client.config.PaymentMethodClient.readTimeout: 200 +feign.client.config.PaymentMethodClient.connectTimeout: 50 +feign.client.config.ReportClient.readTimeout: 100 +feign.client.config.ReportClient.connectTimeout: 50 \ No newline at end of file From 8ed270c58ed98582301e975d86d563252f441e62 Mon Sep 17 00:00:00 2001 From: pedrolopes9-7 Date: Tue, 10 Sep 2024 23:32:22 -0300 Subject: [PATCH 4/5] add test and configurations. disable thread test to make it work without retries --- .../PaymentMethodClient.java | 6 +-- .../Purchase.java | 14 ----- .../PurchaseService.java | 8 +-- .../src/main/resources/application.properties | 7 ++- .../PurchaseServiceIntegrationTest.java | 54 +++++++++---------- .../application-integration_test.properties | 4 -- 6 files changed, 39 insertions(+), 54 deletions(-) delete mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java delete mode 100644 spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java index af2d39bb312a..187cdd3f65ee 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java @@ -5,9 +5,9 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -@FeignClient(name = "paymentClient", url = "http://localhost:8083") +@FeignClient(name = "paymentMethodClient", url = "http://localhost:8083") public interface PaymentMethodClient { - @RequestMapping(method = RequestMethod.POST, value = "/purchase") - String processPurchase(@RequestParam(name = "site_id") String siteId); + @RequestMapping(method = RequestMethod.GET, value = "/payment_methods") + String getAvailablePaymentMethods(@RequestParam(name = "site_id") String siteId); } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java deleted file mode 100644 index 99f2ec151cd4..000000000000 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/Purchase.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.baeldung.cloud.openfeign.completablefuturefeignclient; - -public class Purchase { - - String siteId; - - public Purchase(String siteId) { - this.siteId = siteId; - } - - public String getSiteId() { - return siteId; - } -} diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java index dece2920caab..b3db4d248fbe 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java @@ -20,12 +20,12 @@ public PurchaseService(PaymentMethodClient paymentMethodClient, ReportClient rep this.reportClient = reportClient; } - public String executePurchase(Purchase purchase) throws ExecutionException, InterruptedException { - CompletableFuture paymentMethodsFuture = CompletableFuture.supplyAsync(() -> paymentMethodClient.processPurchase(purchase.getSiteId())) + public String executePurchase(String siteId) throws ExecutionException, InterruptedException { + CompletableFuture paymentMethodsFuture = CompletableFuture.supplyAsync(() -> paymentMethodClient.getAvailablePaymentMethods(siteId)) .orTimeout(400, TimeUnit.MILLISECONDS) .exceptionally(ex -> { if (ex.getCause() instanceof FeignException && ((FeignException) ex.getCause()).status() == 404) { - return "account_money"; + return "cash"; } if (ex.getCause() instanceof RetryableException) { @@ -42,7 +42,7 @@ public String executePurchase(Purchase purchase) throws ExecutionException, Inte }); CompletableFuture.runAsync(() -> reportClient.sendReport("Purchase Order Report")) - .orTimeout(1, TimeUnit.SECONDS); + .orTimeout(400, TimeUnit.MILLISECONDS); return String.format("Purchase executed with payment method %s", paymentMethodsFuture.get()); } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/resources/application.properties b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/resources/application.properties index 0a31d79ee035..eb1a2eb36e61 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/resources/application.properties +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/resources/application.properties @@ -7,4 +7,9 @@ spring.main.allow-bean-definition-overriding=true logging.level.com.baeldung.cloud.openfeign.client=INFO feign.hystrix.enabled=true -spring.cloud.openfeign.client.config.postClient.url=https://jsonplaceholder.typicode.com/posts/ \ No newline at end of file +spring.cloud.openfeign.client.config.postClient.url=https://jsonplaceholder.typicode.com/posts/ + +feign.client.config.paymentMethodClient.readTimeout: 200 +feign.client.config.paymentMethodClient.connectTimeout: 100 +feign.client.config.reportClient.readTimeout: 200 +feign.client.config.reportClient.connectTimeout: 100 \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java index 55e57e05417b..93252758528c 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java @@ -4,25 +4,26 @@ import com.github.tomakehurst.wiremock.WireMockServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.openfeign.FeignClientProperties; import org.springframework.http.HttpStatus; -import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; @ExtendWith(SpringExtension.class) @SpringBootTest(classes = ExampleApplication.class) -@TestPropertySource(locations = "classpath:application-integration_test.properties") class PurchaseServiceIntegrationTest { @Autowired @@ -36,8 +37,6 @@ class PurchaseServiceIntegrationTest { private WireMockServer wireMockServer; - private Purchase purchase; - @BeforeEach public void startWireMockServer() { wireMockServer = new WireMockServer(8083); @@ -46,8 +45,6 @@ public void startWireMockServer() { stubFor(post(urlEqualTo("/reports")) .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); - - purchase = new Purchase("BR"); } @AfterEach @@ -58,10 +55,10 @@ public void stopWireMockServer() { @Test void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() throws ExecutionException, InterruptedException { - stubFor(post(urlEqualTo("/purchase?site_id=BR")) + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) .willReturn(aResponse().withStatus(HttpStatus.OK.value()).withBody("credit_card"))); - String result = purchaseService.executePurchase(purchase); + String result = purchaseService.executePurchase("BR"); assertNotNull(result); assertEquals("Purchase executed with payment method credit_card", result); @@ -70,32 +67,33 @@ void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() @Test void givenRestCalls_whenPurchaseReturns404_thenReturnDefault() throws ExecutionException, InterruptedException { - stubFor(post(urlEqualTo("/purchase?site_id=BR")) + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); - String result = purchaseService.executePurchase(purchase); + String result = purchaseService.executePurchase("BR"); assertNotNull(result); - assertEquals("Purchase executed with payment method account_money", result); + assertEquals("Purchase executed with payment method cash", result); } @Test - void givenRestCalls_whenPurchaseCompletableFutureTimeout_thenReturnDefault() { - stubFor(post(urlEqualTo("/purchase?site_id=BR")) - .willReturn(aResponse().withFixedDelay(450))); + @Disabled + void givenRestCalls_whenPurchaseCompletableFutureTimeout_thenThrowNewException() { + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) + .willReturn(aResponse().withFixedDelay(550))); - Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase(purchase)); + Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase("BR")); assertEquals("java.lang.RuntimeException: Thread timeout!", error.getMessage()); } -// @Test -// void givenRestCalls_whenPurchaseRequestWebTimeout_thenReturnDefault() { -// stubFor(post(urlEqualTo("/purchase?site_id=BR")) -// .willReturn(aResponse().withFixedDelay(250))); -// -// Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase(purchase)); -// -// assertEquals("REST call network timeout!", error.getMessage()); -// } + @Test + void givenRestCalls_whenPurchaseRequestWebTimeout_thenThrowNewException() { + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) + .willReturn(aResponse().withFixedDelay(250))); + + Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase("BR")); + + assertEquals("java.lang.RuntimeException: REST call network timeout!", error.getMessage()); + } } \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties deleted file mode 100644 index 41cd030c8b9f..000000000000 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/resources/application-integration_test.properties +++ /dev/null @@ -1,4 +0,0 @@ -feign.client.config.PaymentMethodClient.readTimeout: 200 -feign.client.config.PaymentMethodClient.connectTimeout: 50 -feign.client.config.ReportClient.readTimeout: 100 -feign.client.config.ReportClient.connectTimeout: 50 \ No newline at end of file From 18bfcd9f016724dc059a111dfe5f356c33a8d107 Mon Sep 17 00:00:00 2001 From: pedrolopes9-7 Date: Wed, 11 Sep 2024 21:00:38 -0300 Subject: [PATCH 5/5] identing and reorering imports --- .../PurchaseService.java | 41 ++++++++++--------- .../PurchaseServiceIntegrationTest.java | 23 ++++------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java index b3db4d248fbe..b37399072882 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java @@ -1,14 +1,15 @@ package com.baeldung.cloud.openfeign.completablefuturefeignclient; -import feign.FeignException; -import feign.RetryableException; -import org.springframework.stereotype.Service; - import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.springframework.stereotype.Service; + +import feign.FeignException; +import feign.RetryableException; + @Service public class PurchaseService { @@ -22,27 +23,27 @@ public PurchaseService(PaymentMethodClient paymentMethodClient, ReportClient rep public String executePurchase(String siteId) throws ExecutionException, InterruptedException { CompletableFuture paymentMethodsFuture = CompletableFuture.supplyAsync(() -> paymentMethodClient.getAvailablePaymentMethods(siteId)) - .orTimeout(400, TimeUnit.MILLISECONDS) - .exceptionally(ex -> { - if (ex.getCause() instanceof FeignException && ((FeignException) ex.getCause()).status() == 404) { - return "cash"; - } + .orTimeout(400, TimeUnit.MILLISECONDS) + .exceptionally(ex -> { + if (ex.getCause() instanceof FeignException && ((FeignException) ex.getCause()).status() == 404) { + return "cash"; + } - if (ex.getCause() instanceof RetryableException) { - // handle REST timeout - throw new RuntimeException("REST call network timeout!"); - } + if (ex.getCause() instanceof RetryableException) { + // handle REST timeout + throw new RuntimeException("REST call network timeout!"); + } - if (ex instanceof TimeoutException) { - // handle thread timeout - throw new RuntimeException("Thread timeout!", ex); - } + if (ex instanceof TimeoutException) { + // handle thread timeout + throw new RuntimeException("Thread timeout!", ex); + } - throw new RuntimeException("Unrecoverable error!", ex); - }); + throw new RuntimeException("Unrecoverable error!", ex); + }); CompletableFuture.runAsync(() -> reportClient.sendReport("Purchase Order Report")) - .orTimeout(400, TimeUnit.MILLISECONDS); + .orTimeout(400, TimeUnit.MILLISECONDS); return String.format("Purchase executed with payment method %s", paymentMethodsFuture.get()); } diff --git a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java index 93252758528c..351be0cf3a8d 100644 --- a/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java +++ b/spring-cloud-modules/spring-cloud-openfeign-2/src/test/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java @@ -2,6 +2,7 @@ import com.baeldung.cloud.openfeign.ExampleApplication; import com.github.tomakehurst.wiremock.WireMockServer; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -43,8 +44,7 @@ public void startWireMockServer() { configureFor("localhost", 8083); wireMockServer.start(); - stubFor(post(urlEqualTo("/reports")) - .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); + stubFor(post(urlEqualTo("/reports")).willReturn(aResponse().withStatus(HttpStatus.OK.value()))); } @AfterEach @@ -53,10 +53,9 @@ public void stopWireMockServer() { } @Test - void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() - throws ExecutionException, InterruptedException { - stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) - .willReturn(aResponse().withStatus(HttpStatus.OK.value()).withBody("credit_card"))); + void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() throws ExecutionException, InterruptedException { + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withStatus(HttpStatus.OK.value()) + .withBody("credit_card"))); String result = purchaseService.executePurchase("BR"); @@ -65,10 +64,8 @@ void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() } @Test - void givenRestCalls_whenPurchaseReturns404_thenReturnDefault() - throws ExecutionException, InterruptedException { - stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) - .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + void givenRestCalls_whenPurchaseReturns404_thenReturnDefault() throws ExecutionException, InterruptedException { + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); String result = purchaseService.executePurchase("BR"); @@ -79,8 +76,7 @@ void givenRestCalls_whenPurchaseReturns404_thenReturnDefault() @Test @Disabled void givenRestCalls_whenPurchaseCompletableFutureTimeout_thenThrowNewException() { - stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) - .willReturn(aResponse().withFixedDelay(550))); + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withFixedDelay(550))); Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase("BR")); @@ -89,8 +85,7 @@ void givenRestCalls_whenPurchaseCompletableFutureTimeout_thenThrowNewException() @Test void givenRestCalls_whenPurchaseRequestWebTimeout_thenThrowNewException() { - stubFor(get(urlEqualTo("/payment_methods?site_id=BR")) - .willReturn(aResponse().withFixedDelay(250))); + stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withFixedDelay(250))); Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase("BR"));