From 8a23e524611ced51cd268612c7ee8ea6184a7364 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:21:16 -0500 Subject: [PATCH 1/4] SWI-6678 Implement `getOrdersByCustomerOrderId` Method --- .../bandwidth/sdk/numbers/NumbersClient.java | 9 +++ .../sdk/numbers/NumbersClientImpl.java | 29 +++++++++ .../numbers/models/orders/OrdersResponse.java | 26 ++++++++ .../models/orders/OrdersResponseData.java | 62 +++++++++++++++++++ .../models/orders/OrdersResponseLinks.java | 29 +++++++++ .../models/orders/OrdersResponseWrapper.java | 36 +++++++++++ .../sdk/numbers/NumbersClientImplTest.java | 62 +++++++++++++++++-- 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponse.java create mode 100644 src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java create mode 100644 src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseLinks.java create mode 100644 src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseWrapper.java diff --git a/src/main/java/com/bandwidth/sdk/numbers/NumbersClient.java b/src/main/java/com/bandwidth/sdk/numbers/NumbersClient.java index c37606e..3e1acba 100644 --- a/src/main/java/com/bandwidth/sdk/numbers/NumbersClient.java +++ b/src/main/java/com/bandwidth/sdk/numbers/NumbersClient.java @@ -4,6 +4,7 @@ import com.bandwidth.sdk.numbers.models.orders.Order; import com.bandwidth.sdk.numbers.models.orders.OrderResponse; import com.bandwidth.sdk.numbers.models.AvailableNumberSearchRequest; +import com.bandwidth.sdk.numbers.models.orders.OrdersResponse; public interface NumbersClient extends AutoCloseable { @@ -34,4 +35,12 @@ public interface NumbersClient extends AutoCloseable { * @return {@link OrderResponse} with the details of the results of placing the order */ OrderResponse getOrderStatus(String orderId); + + /** + * Fetch the details of all orders. + * + * @param customerOrderId The customerOrderId of the order to search on + * @return {@link OrdersResponse} with the details of all orders + */ + OrdersResponse getOrdersByCustomerOrderId(String customerOrderId); } diff --git a/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java b/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java index db43173..26f34f6 100644 --- a/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java +++ b/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java @@ -1,12 +1,14 @@ package com.bandwidth.sdk.numbers; import com.bandwidth.sdk.numbers.exception.ExceptionUtils; +import com.bandwidth.sdk.numbers.exception.NumbersApiException; import com.bandwidth.sdk.numbers.helpers.RetryableRequest; import com.bandwidth.sdk.numbers.helpers.SleepRetryPolicy; import com.bandwidth.sdk.numbers.models.AvailableNumberSearchRequest; import com.bandwidth.sdk.numbers.models.SearchResult; import com.bandwidth.sdk.numbers.models.orders.Order; import com.bandwidth.sdk.numbers.models.orders.OrderResponse; +import com.bandwidth.sdk.numbers.models.orders.OrdersResponse; import com.bandwidth.sdk.numbers.serde.NumbersSerde; import com.google.common.base.Preconditions; import io.netty.handler.codec.http.HttpHeaderNames; @@ -105,6 +107,33 @@ public OrderResponse getOrderStatus(String orderId) { }); } + @Override + public OrdersResponse getOrdersByCustomerOrderId(String customerOrderId){ + return getOrdersByCustomerOrderIdAsync(customerOrderId).join(); + } + + private CompletableFuture getOrdersByCustomerOrderIdAsync(String customerOrderId) throws NumbersApiException { + // arbitrary + String pageSize = "100"; + + return catchAsyncClientExceptions(() -> { + String url = MessageFormat.format("{0}/accounts/{1}/orders?customerOrderId={2}&page=1&size={3}", baseUrl, account, customerOrderId, pageSize); + return httpClient.prepareGet(url) + .execute() + .toCompletableFuture() + .thenApply(resp -> { + String responseBodyString = resp.getResponseBody(StandardCharsets.UTF_8); + if (resp.getStatusCode() != 200) { + if (resp.getStatusCode() == 204) { + throw new NumbersApiException("No orders found"); + } + throw new NumbersApiException("Error fetching orders: " + responseBodyString); + } + return NumbersSerde.deserialize(responseBodyString, OrdersResponse.class); + }); + }); + } + @Override public void close() { try { diff --git a/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponse.java b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponse.java new file mode 100644 index 0000000..3af9d4a --- /dev/null +++ b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponse.java @@ -0,0 +1,26 @@ +package com.bandwidth.sdk.numbers.models.orders; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import org.immutables.value.Value; + +import javax.annotation.Nullable; + +@Value.Immutable +@JsonSerialize(as = ImmutableOrdersResponse.class) +@JsonDeserialize(as = ImmutableOrdersResponse.class) +@JacksonXmlRootElement(localName = "ResponseSelectWrapper") +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class OrdersResponse { + @Value.Redacted + @Nullable + @JacksonXmlProperty(localName = "ListOrderIdUserIdDate") + public abstract OrdersResponseWrapper getOrdersResponseData(); + + public static ImmutableOrdersResponse.Builder builder() { + return ImmutableOrdersResponse.builder(); + } +} diff --git a/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java new file mode 100644 index 0000000..ead07d3 --- /dev/null +++ b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java @@ -0,0 +1,62 @@ +package com.bandwidth.sdk.numbers.models.orders; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import org.immutables.value.Value; + +import javax.annotation.Nullable; +import java.util.Date; + +@Value.Immutable +@JsonSerialize(as = ImmutableOrdersResponseData.class) +@JsonDeserialize(as = ImmutableOrdersResponseData.class) +@JacksonXmlRootElement(localName = "OrderIdUserIdDate") +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class OrdersResponseData { + @Nullable + @JacksonXmlProperty(localName = "accountId") + public abstract String getAccountId(); + + @Nullable + @JacksonXmlProperty(localName = "CountOfTNs") + public abstract Integer getCountOfTns(); + + @Nullable + @JacksonXmlProperty(localName = "CustomerOrderId") + public abstract String getCustomerOrderId(); + + @Nullable + @JacksonXmlProperty(localName = "userId") + public abstract String getUserId(); + + @Nullable + @JacksonXmlProperty(localName = "lastModifiedDate") + public abstract Date getLastModifiedDate(); + + @Nullable + @JacksonXmlProperty(localName = "OrderDate") + public abstract Date getOrderDate(); + + @Nullable + @JacksonXmlProperty(localName = "OrderType") + public abstract String getOrderType(); + + @Nullable + @JacksonXmlProperty(localName = "orderId") + public abstract String getOrderId(); + + @Nullable + @JacksonXmlProperty(localName = "OrderStatus") + public abstract String getOrderStatus(); + + @Nullable + @JacksonXmlProperty(localName = "Summary") + public abstract String getSummary(); + + public static ImmutableOrdersResponseData.Builder builder() { + return ImmutableOrdersResponseData.builder(); + } +} diff --git a/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseLinks.java b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseLinks.java new file mode 100644 index 0000000..eeb5be2 --- /dev/null +++ b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseLinks.java @@ -0,0 +1,29 @@ +package com.bandwidth.sdk.numbers.models.orders; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import org.immutables.value.Value; + +import javax.annotation.Nullable; + +@Value.Immutable +@JsonSerialize(as = ImmutableOrdersResponseLinks.class) +@JsonDeserialize(as = ImmutableOrdersResponseLinks.class) +@JacksonXmlRootElement(localName = "Links") +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class OrdersResponseLinks { + @Nullable + @JacksonXmlProperty(localName = "first") + public abstract String getFirst(); + + @Nullable + @JacksonXmlProperty(localName = "next") + public abstract String getNext(); + + public static ImmutableOrdersResponseLinks.Builder builder() { + return ImmutableOrdersResponseLinks.builder(); + } +} diff --git a/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseWrapper.java b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseWrapper.java new file mode 100644 index 0000000..adbde65 --- /dev/null +++ b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseWrapper.java @@ -0,0 +1,36 @@ +package com.bandwidth.sdk.numbers.models.orders; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import org.immutables.value.Value; + +import javax.annotation.Nullable; +import java.util.List; + +@Value.Immutable +@JsonSerialize(as = ImmutableOrdersResponseWrapper.class) +@JsonDeserialize(as = ImmutableOrdersResponseWrapper.class) +@JacksonXmlRootElement(localName = "ListOrderIdUserIdDate") +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class OrdersResponseWrapper { + @Nullable + @JacksonXmlProperty(localName = "TotalCount") + public abstract Integer getTotalCount(); + + @Nullable + @JacksonXmlElementWrapper(localName = "Links") + public abstract OrdersResponseLinks getLinks(); + + @Nullable + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "OrderIdUserIdDate") + public abstract List getOrders(); + + public static ImmutableOrdersResponseWrapper.Builder builder() { + return ImmutableOrdersResponseWrapper.builder(); + } +} diff --git a/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java b/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java index 4e4607e..7aea0f5 100644 --- a/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java +++ b/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java @@ -6,10 +6,7 @@ import com.bandwidth.sdk.numbers.models.ImmutableErrorResponse; import com.bandwidth.sdk.numbers.models.ImmutableSearchResult; import com.bandwidth.sdk.numbers.models.SearchResult; -import com.bandwidth.sdk.numbers.models.orders.ExistingTelephoneNumberOrderType; -import com.bandwidth.sdk.numbers.models.orders.ImmutableOrderResponse; -import com.bandwidth.sdk.numbers.models.orders.Order; -import com.bandwidth.sdk.numbers.models.orders.OrderResponse; +import com.bandwidth.sdk.numbers.models.orders.*; import com.bandwidth.sdk.numbers.serde.NumbersSerde; import org.assertj.core.api.Assertions; import org.asynchttpclient.AsyncHttpClient; @@ -23,6 +20,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -80,6 +78,23 @@ public class NumbersClientImplTest { .order(order) .build(); + private final OrdersResponse ordersResponse = ImmutableOrdersResponse.builder() + .ordersResponseData(OrdersResponseWrapper.builder() + .addOrders(ImmutableOrdersResponseData.builder() + .accountId("1") + .countOfTns(1) + .customerOrderId("foo") + .userId("1") + .lastModifiedDate(null) + .orderDate(null) + .orderType("1") + .orderId("1") + .orderStatus("1") + .summary("1") + .build()) + .build()) + .build(); + @Mock private AsyncHttpClient asyncHttpClient; @Mock @@ -179,4 +194,43 @@ public void orderNumbersWithError() { assertThat(thrown).isInstanceOf(NumbersApiException.class); } + @Test + public void searchForNumberByCustomerOrderId() { + when(asyncHttpClient.prepareGet(anyString())).thenReturn(boundRequestBuilder); + when(boundRequestBuilder.execute()).thenReturn(listenableFuture); + when(listenableFuture.toCompletableFuture()).thenReturn(CompletableFuture.completedFuture(response)); + when(response.getStatusCode()).thenReturn(200); + when(response.getResponseBody(StandardCharsets.UTF_8)).thenReturn(NumbersSerde.serialize(ordersResponse)); + + assertThat(ordersResponse).isEqualTo(numbersClient.getOrdersByCustomerOrderId("foo")); + List orders = ordersResponse.getOrdersResponseData().getOrders(); + assertThat(orders).hasSize(1); + assertThat(orders.get(0).getCustomerOrderId()).isEqualTo("foo"); + assertThat(orders.get(0).getOrderId()).isEqualTo("1"); + + verify(asyncHttpClient, times(1)).prepareGet(anyString()); + } + + @Test + public void searchForNumberByCustomerOrderIdNoOrdersFound() { + when(asyncHttpClient.prepareGet(anyString())).thenReturn(boundRequestBuilder); + when(boundRequestBuilder.execute()).thenReturn(listenableFuture); + when(listenableFuture.toCompletableFuture()).thenReturn(CompletableFuture.completedFuture(response)); + when(response.getStatusCode()).thenReturn(204); + + Throwable thrown = Assertions.catchThrowable(() -> numbersClient.getOrdersByCustomerOrderId("foo")); + assertThat(thrown).isInstanceOf(CompletionException.class).hasCauseInstanceOf(NumbersApiException.class); + } + + @Test + public void searchForNumberByCustomerOrderIdRequestError() { + when(asyncHttpClient.prepareGet(anyString())).thenReturn(boundRequestBuilder); + when(boundRequestBuilder.execute()).thenReturn(listenableFuture); + when(listenableFuture.toCompletableFuture()).thenReturn(CompletableFuture.completedFuture(response)); + when(response.getStatusCode()).thenReturn(400); + + Throwable thrown = Assertions.catchThrowable(() -> numbersClient.getOrdersByCustomerOrderId("foo")); + assertThat(thrown).isInstanceOf(CompletionException.class).hasCauseInstanceOf(NumbersApiException.class); + } + } From 1ebbfbcb0ba638af470cdbf1e403736c570bb061 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:22:52 -0500 Subject: [PATCH 2/4] A note --- .../java/com/bandwidth/sdk/numbers/NumbersClientImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java b/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java index 26f34f6..dd34df5 100644 --- a/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java +++ b/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java @@ -107,6 +107,14 @@ public OrderResponse getOrderStatus(String orderId) { }); } + /** + * This queries the /orders API with a required parameter of customerOrderId + * If we need to extend this for any reason, we should implement a getOrders method with a builder for the possible query parameters + * The HTTP client will need to be investigated to see how it will support adding multiple query parameters + * + * @param customerOrderId The customerOrderId of the order to search on + * @return The orders response + */ @Override public OrdersResponse getOrdersByCustomerOrderId(String customerOrderId){ return getOrdersByCustomerOrderIdAsync(customerOrderId).join(); From 56e8ec7ea6bfc602c83785d801ac36b4401451dc Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:26:21 -0500 Subject: [PATCH 3/4] add status code to exception message --- src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java b/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java index dd34df5..df0feee 100644 --- a/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java +++ b/src/main/java/com/bandwidth/sdk/numbers/NumbersClientImpl.java @@ -135,7 +135,7 @@ private CompletableFuture getOrdersByCustomerOrderIdAsync(String if (resp.getStatusCode() == 204) { throw new NumbersApiException("No orders found"); } - throw new NumbersApiException("Error fetching orders: " + responseBodyString); + throw new NumbersApiException("Error fetching orders. Response body: " + responseBodyString + "; Status code: " + resp.getStatusCode()); } return NumbersSerde.deserialize(responseBodyString, OrdersResponse.class); }); From fb500308f037f9fab086a88c5ff536a58e4feaae Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:48:50 -0500 Subject: [PATCH 4/4] Use existing OrderStatus enum --- .../bandwidth/sdk/numbers/models/orders/OrdersResponseData.java | 2 +- .../java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java index ead07d3..5d235f3 100644 --- a/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java +++ b/src/main/java/com/bandwidth/sdk/numbers/models/orders/OrdersResponseData.java @@ -50,7 +50,7 @@ public abstract class OrdersResponseData { @Nullable @JacksonXmlProperty(localName = "OrderStatus") - public abstract String getOrderStatus(); + public abstract OrderResponse.OrderStatus getOrderStatus(); @Nullable @JacksonXmlProperty(localName = "Summary") diff --git a/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java b/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java index 7aea0f5..cedd87f 100644 --- a/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java +++ b/src/test/java/com/bandwidth/sdk/numbers/NumbersClientImplTest.java @@ -89,7 +89,7 @@ public class NumbersClientImplTest { .orderDate(null) .orderType("1") .orderId("1") - .orderStatus("1") + .orderStatus(OrderResponse.OrderStatus.COMPLETE) .summary("1") .build()) .build())