diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 02b0b4f..ce35b53 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -37,9 +37,10 @@ jobs:
with:
java-version: ${{ matrix.java-version }}
architecture: x64
+ distribution: "adopt"
- name: Lint the code
- run: mvn checkstyle:checkstyle
+ run: mvn -X checkstyle:checkstyle
- name: Run test suite
run: mvn test
diff --git a/pom.xml b/pom.xml
index 93b8715..ed67987 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,12 +36,34 @@
+
+ org.mock-server
+ mockserver-netty
+ 3.10.8
+
+
+ org.mock-server
+ mockserver-client-java
+ 3.10.8
+
junit
junit
4.11
test
+
+ com.google.code.gson
+ gson
+ 2.8.8
+ compile
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.7.2
+ test
+
@@ -107,7 +129,44 @@
+
+ org.mock-server
+ mockserver-maven-plugin
+ 3.10.8
+
+ 1080
+ 1090
+ OFF
+ org.mockserver.maven.ExampleInitializationClass
+
+
+
+ process-test-classes
+ process-test-classes
+
+ start
+
+
+
+ verify
+ verify
+
+ stop
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 11
+
+
+
diff --git a/src/main/java/com/shipengine/Config.java b/src/main/java/com/shipengine/Config.java
index d144450..c724ebe 100644
--- a/src/main/java/com/shipengine/Config.java
+++ b/src/main/java/com/shipengine/Config.java
@@ -1,26 +1,127 @@
package com.shipengine;
+import com.shipengine.exception.InvalidFieldValueException;
+import com.shipengine.exception.ValidationException;
+import com.shipengine.util.Constants;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public class Config {
+ private String apiKey;
+
+ public void setBaseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ private String baseUrl = Constants.BASE_URL;
+ private int pageSize = 5000;
+ private int retries = 1;
+ private int timeout = 50;
+
+ public Config(Map config) {
+ if (config.containsKey("apiKey")) {
+ setApiKey(config.get("apiKey").toString());
+ } else {
+ setApiKey("");
+ }
+
+ if (config.containsKey("baseUrl")) {
+ setBaseUrl(config.get("baseUrl").toString());
+ }
+
+ if (config.containsKey("timeout")) {
+ setTimeout(Integer.parseInt(config.get("timeout").toString()));
+ }
+
+ if (config.containsKey("retries")) {
+ setRetries(Integer.parseInt(config.get("retries").toString()));
+ }
+
+ if (config.containsKey("pageSize")) {
+ setPageSize(Integer.parseInt(config.get("pageSize").toString()));
+ }
+ }
+
+ public Config(String apiKey) {
+ setApiKey(apiKey);
+ }
+
+ public Config(String apiKey, int timeout, int retries, int pageSize) {
+ setApiKey(apiKey);
+ setTimeout(timeout);
+ setRetries(retries);
+ setPageSize(pageSize);
+ }
+
+ public Config(String apiKey, String baseUrl, int timeout, int retries, int pageSize) {
+ setApiKey(apiKey);
+ setBaseUrl(baseUrl);
+ setTimeout(timeout);
+ setRetries(retries);
+ setPageSize(pageSize);
+ }
+
+ /*
+ * The URL of the ShipEngine API. You can usually leave this unset and it will
+ * default to our public API.
+ */
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
/*
* Your ShipEngine API key. This can be a production or sandbox key. Sandbox
* keys start with "TEST_".
*/
- String apiKey;
+ public String getApiKey() {
+ return apiKey;
+ }
/*
- * The URL of the ShipEngine API. You can usually leave this unset and it will
- * default to our public API.
+ * Set the ShipEngine API key.
*/
- String baseUrl = "https://api.shipengine.com/";
+ public void setApiKey(String apiKey) throws InvalidFieldValueException {
+ String apiKeyStr = "apiKey";
+ Pattern regexPattern = Pattern.compile("[\\s]");
+ Matcher matcher = regexPattern.matcher(apiKey);
+ if (apiKey.length() == 0) {
+ throw new InvalidFieldValueException(apiKeyStr, apiKey);
+ } else if (matcher.matches()) {
+ throw new InvalidFieldValueException(apiKeyStr, apiKey);
+ } else {
+ this.apiKey = apiKey;
+ }
+ }
/*
- * Some ShipEngine API endpoints return paged data. This lets you control the
- * number of items returned per request. Larger numbers will use more memory but
- * will require fewer HTTP requests.
+ * The maximum amount of time (in milliseconds) to wait for a response from the
+ * ShipEngine server.
*
- * Defaults to 50.
+ * Defaults to 5000 (5 seconds).
*/
- int pageSize;
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /*
+ * Set the timeout (in milliseconds).
+ */
+ public void setTimeout(int timeout) {
+ if (timeout == 0) {
+ throw new ValidationException(
+ "The timeout value cannot be zero.",
+ "shipengine",
+ "validation",
+ "invalid_field_value"
+ );
+ }
+ this.timeout = timeout;
+ }
/*
* If the ShipEngine client receives a rate limit error it can automatically
@@ -30,40 +131,95 @@ public class Config {
* Defaults to 1, which means up to 2 attempts will be made (the original
* attempt, plus one retry).
*/
- int retries;
+ public int getRetries() {
+ return retries;
+ }
/*
- * The maximum amount of time (in milliseconds) to wait for a response from the
- * ShipEngine server.
- *
- * Defaults to 5000 (5 seconds).
+ * Set the retries.
*/
- int timeout;
-
- public Config(String apiKey) {
- this.apiKey = apiKey;
- this.timeout = 5000;
- this.retries = 1;
- this.pageSize = 50;
+ public void setRetries(int retries) {
+ if (retries == 0) {
+ throw new ValidationException(
+ "The retries value cannot be zero.",
+ "shipengine",
+ "validation",
+ "invalid_field_value"
+ );
+ }
+ this.retries = retries;
}
- public String getBaseUrl() {
- return baseUrl;
+ /*
+ * Some ShipEngine API endpoints return paged data. This lets you control the
+ * number of items returned per request. Larger numbers will use more memory but
+ * will require fewer HTTP requests.
+ *
+ * Defaults to 50.
+ */
+ public int getPageSize() {
+ return pageSize;
}
- public String getApiKey() {
- return apiKey;
+ /*
+ * Set the page size.
+ */
+ public void setPageSize(int pageSize) {
+ if (pageSize == 0) {
+ throw new ValidationException(
+ "The pageSize value cannot be zero.",
+ "shipengine",
+ "validation",
+ "invalid_field_value"
+ );
+ }
+ this.pageSize = pageSize;
}
- public int getTimeout() {
- return timeout;
+ public Config merge() {
+ return this;
}
- public int getRetries() {
- return retries;
+ public Config merge(String apiKey) {
+ return new Config(apiKey);
}
- public int getPageSize() {
- return pageSize;
+ public Config merge(Map newConfig) {
+ Map config = new HashMap<>();
+ List configKeys = Arrays.asList("apiKey", "timeout", "retries", "pageSize");
+
+ if (newConfig.isEmpty()) {
+ return this;
+ } else {
+ if (newConfig.containsKey(configKeys.get(0))) {
+ config.put(configKeys.get(0), newConfig.get(configKeys.get(0)));
+ } else {
+ config.put(configKeys.get(0), getApiKey());
+ }
+
+ if (newConfig.containsKey(configKeys.get(1))) {
+ config.put(configKeys.get(1), newConfig.get(configKeys.get(1)));
+ } else {
+ config.put(configKeys.get(1), getTimeout());
+ }
+
+ if (newConfig.containsKey(configKeys.get(2))) {
+ config.put(configKeys.get(2), newConfig.get(configKeys.get(2)));
+ } else {
+ config.put(configKeys.get(2), getRetries());
+ }
+
+ if (newConfig.containsKey(configKeys.get(3))) {
+ config.put(configKeys.get(3), newConfig.get(configKeys.get(3)));
+ } else {
+ config.put(configKeys.get(3), getPageSize());
+ }
+ }
+ return new Config(
+ config.get(configKeys.get(0)).toString(),
+ Integer.parseInt(config.get(configKeys.get(1)).toString()),
+ Integer.parseInt(config.get(configKeys.get(2)).toString()),
+ Integer.parseInt(config.get(configKeys.get(3)).toString())
+ );
}
}
diff --git a/src/main/java/com/shipengine/InternalClient.java b/src/main/java/com/shipengine/InternalClient.java
new file mode 100644
index 0000000..93a2164
--- /dev/null
+++ b/src/main/java/com/shipengine/InternalClient.java
@@ -0,0 +1,475 @@
+package com.shipengine;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.shipengine.exception.ClientTimeoutError;
+import com.shipengine.exception.RateLimitExceededException;
+import com.shipengine.exception.ShipEngineException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class InternalClient {
+ private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+ /**
+ * Enumeration of frequently used HTTP verbs to be used when making requests with the client.
+ */
+ private enum HttpVerbs {
+ GET,
+ POST,
+ PUT,
+ DELETE
+ }
+
+ /**
+ * This is the request loop that manages the clients retry logic when interacting with ShipEngine API.
+ * This method takes in the request body as a Map or HashMap.
+ *
+ * @param httpMethod The HTTP Verb to set as the HTTP Method in the request.
+ * @param endpoint A string representation of the target API endpoint for the request.
+ * @param body A Map or HashMap that contains the request body contents.
+ * @param config The global Config object for the ShipEngine SDK.
+ * @return Map The response from ShipEngine API serialized into a Map/HashMap.
+ */
+ private Map requestLoop(
+ String httpMethod,
+ String endpoint,
+ Map body,
+ Config config
+ ) throws InterruptedException {
+ int retry = 0;
+ Map apiResponse = Map.of();
+ while (retry <= config.getRetries()) {
+ try {
+ apiResponse = sendHttpRequest(
+ httpMethod,
+ endpoint,
+ body,
+ config
+ );
+ } catch (Exception err) {
+ if ((retry < config.getRetries()) &&
+ (err instanceof RateLimitExceededException) &&
+ (config.getTimeout() > ((RateLimitExceededException) err).getRetryAfter())) {
+ try {
+ java.util.concurrent.TimeUnit.SECONDS.sleep(((RateLimitExceededException) err).getRetryAfter());
+ retry++;
+ // continue;
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ } else {
+ throw err;
+ }
+ }
+ retry++;
+ }
+ return apiResponse;
+ }
+
+ /**
+ * This is the request loop that manages the clients retry logic when interacting with ShipEngine API.
+ * This method override takes in the request body as a List or Array.
+ *
+ * @param httpMethod The HTTP Verb to set as the HTTP Method in the request.
+ * @param endpoint A string representation of the target API endpoint for the request.
+ * @param body A Map or HashMap that contains the request body contents.
+ * @param config The global Config object for the ShipEngine SDK.
+ * @return List The response from ShipEngine API serialized into a List/Array.
+ */
+ private List> requestLoop(
+ String httpMethod,
+ String endpoint,
+ List> body,
+ Config config
+ ) throws InterruptedException {
+ int retry = 0;
+ List apiResponse = List.of();
+ while (retry <= config.getRetries()) {
+ try {
+ apiResponse = sendHttpRequest(
+ httpMethod,
+ endpoint,
+ body,
+ config
+ );
+ } catch (Exception err) {
+ if ((retry < config.getRetries()) &&
+ (err instanceof RateLimitExceededException) &&
+ (config.getTimeout() > ((RateLimitExceededException) err).getRetryAfter())) {
+ try {
+ java.util.concurrent.TimeUnit.SECONDS.sleep(((RateLimitExceededException) err).getRetryAfter());
+ retry++;
+ // continue;
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ } else {
+ throw err;
+ }
+ }
+ retry++;
+ }
+ return apiResponse;
+ }
+
+ /**
+ * This is the request loop that manages the clients retry logic when interacting with ShipEngine API.
+ * This method override does not take in a *body* argument (e.g. Servicing a GET request).
+ *
+ * @param httpMethod The HTTP Verb to set as the HTTP Method in the request.
+ * @param endpoint A string representation of the target API endpoint for the request.
+ * @param config The global Config object for the ShipEngine SDK.
+ * @return Map The response from ShipEngine API serialized into a List/Array.
+ */
+ private Map requestLoop(
+ String httpMethod,
+ String endpoint,
+ Config config
+ ) throws InterruptedException {
+ int retry = 0;
+ Map apiResponse = Map.of();
+ while (retry <= config.getRetries()) {
+ try {
+ apiResponse = sendHttpRequest(
+ httpMethod,
+ endpoint,
+ config
+ );
+ } catch (Exception err) {
+ if ((retry < config.getRetries()) &&
+ (err instanceof RateLimitExceededException) &&
+ (config.getTimeout() > ((RateLimitExceededException) err).getRetryAfter())) {
+ try {
+ java.util.concurrent.TimeUnit.SECONDS.sleep(((RateLimitExceededException) err).getRetryAfter());
+ retry++;
+ // continue;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ throw err;
+ }
+ }
+ retry++;
+ }
+ return apiResponse;
+ }
+
+ private Map sendHttpRequest(
+ String httpMethod,
+ String endpoint,
+ Map requestBody,
+ Config config
+ ) {
+ Map apiResponse = Map.of();
+ if (httpMethod.equals(HttpVerbs.POST.name())) {
+ apiResponse = internalPost(endpoint, requestBody, config);
+ } else if (httpMethod.equals(HttpVerbs.GET.name())) {
+ apiResponse = internalGet(endpoint, config);
+ } else if (httpMethod.equals(HttpVerbs.PUT.name())) {
+ apiResponse = internalPut(endpoint, requestBody, config);
+ } else if (httpMethod.equals(HttpVerbs.DELETE.name())) {
+ apiResponse = internalDelete(endpoint, config);
+ }
+ return apiResponse;
+ }
+
+ private Map sendHttpRequest(
+ String httpMethod,
+ String endpoint,
+ Config config
+ ) {
+ Map apiResponse = Map.of();
+ if (httpMethod.equals(HttpVerbs.GET.name())) {
+ apiResponse = internalGet(endpoint, config);
+ } else if (httpMethod.equals(HttpVerbs.DELETE.name())) {
+ apiResponse = internalDelete(endpoint, config);
+ }
+ return apiResponse;
+ }
+
+ private List> sendHttpRequest(
+ String httpMethod,
+ String endpoint,
+ List> requestBody,
+ Config config
+ ) {
+ List> apiResponse = List.of();
+ if (httpMethod.equals(HttpVerbs.POST.name())) {
+ apiResponse = internalPost(endpoint, requestBody, config);
+ }
+ return apiResponse;
+ }
+
+ public List> post(
+ String endpoint,
+ List> body,
+ Config config
+ ) throws InterruptedException {
+ return requestLoop(
+ HttpVerbs.POST.name(),
+ endpoint,
+ body,
+ config
+ );
+ }
+
+ public Map post(
+ String endpoint,
+ Map body,
+ Config config
+ ) throws InterruptedException {
+ return requestLoop(
+ HttpVerbs.POST.name(),
+ endpoint,
+ body,
+ config
+ );
+ }
+
+ public Map put(
+ String endpoint,
+ Map body,
+ Config config
+ ) throws InterruptedException {
+ return requestLoop(
+ HttpVerbs.PUT.name(),
+ endpoint,
+ body,
+ config
+ );
+ }
+
+ public Map get(
+ String endpoint,
+ Config config
+ ) throws InterruptedException {
+ return requestLoop(
+ HttpVerbs.GET.name(),
+ endpoint,
+ config
+ );
+ }
+
+ public Map delete(
+ String endpoint,
+ Config config
+ ) throws InterruptedException {
+ return requestLoop(
+ HttpVerbs.DELETE.name(),
+ endpoint,
+ config
+ );
+ }
+
+ private HttpRequest.Builder prepareRequest(
+ String endpoint,
+ Config config
+ ) {
+ Pattern pattern = Pattern.compile("/");
+ String baseUri;
+ if (endpoint.length() > 0) {
+ Matcher matcher = pattern.matcher(endpoint);
+ String result = matcher.replaceFirst("");
+ baseUri = String.format("%s%s", config.getBaseUrl(), result);
+
+ } else {
+ baseUri = config.getBaseUrl();
+
+ }
+
+ URI clientUri = null;
+ try {
+ clientUri = new URI(baseUri);
+ } catch (URISyntaxException err) {
+ err.printStackTrace();
+ }
+
+ return HttpRequest.newBuilder()
+ .uri(clientUri)
+ .headers("Content-Type", "application/json")
+ .headers("Accepts", "application/json")
+ .headers("Api-Key", config.getApiKey())
+ .timeout(Duration.of(config.getTimeout(), ChronoUnit.SECONDS));
+
+ }
+
+ private String sendPreparedRequest(
+ HttpRequest preparedRequest,
+ Config config
+ ) {
+ String responseBody = null;
+
+ try {
+ HttpResponse response = HttpClient
+ .newBuilder()
+ .build()
+ .send(preparedRequest, HttpResponse.BodyHandlers.ofString());
+ responseBody = response.body();
+ String retryAfterHeaderValue = response.headers().firstValue("retry-after").toString();
+
+ checkResponseForErrors(
+ response.statusCode(),
+ responseBody,
+ retryAfterHeaderValue,
+ config
+ );
+
+ String debugg = "Check above values in debug console!";
+ } catch (IOException | InterruptedException err) {
+ err.printStackTrace();
+ }
+
+ return responseBody;
+ }
+
+ private Map internalDelete(
+ String endpoint,
+ Config config
+ ) {
+ HttpRequest request = prepareRequest(endpoint, config)
+ .DELETE()
+ .build();
+
+ String apiResponse = sendPreparedRequest(request, config);
+ return apiResponseToMap(apiResponse);
+ }
+
+ private Map internalGet(
+ String endpoint,
+ Config config
+ ) {
+ HttpRequest request = prepareRequest(endpoint, config)
+ .GET()
+ .build();
+
+ String apiResponse = sendPreparedRequest(request, config);
+ return apiResponseToMap(apiResponse);
+ }
+
+ private List> internalPost(
+ String endpoint,
+ List> requestBody,
+ Config config
+ ) {
+ String preppedRequest = gson.toJson(requestBody);
+ HttpRequest request = prepareRequest(endpoint, config)
+ .POST(HttpRequest.BodyPublishers.ofString(preppedRequest))
+ .build();
+
+ String apiResponse = sendPreparedRequest(request, config);
+ return apiResponseToList(apiResponse);
+ }
+
+ private Map internalPost(
+ String endpoint,
+ Map requestBody,
+ Config config
+ ) {
+ HttpRequest request = prepareRequest(endpoint, config)
+ .POST(HttpRequest.BodyPublishers.ofString(hashMapToJson(requestBody)))
+ .build();
+
+ String apiResponse = sendPreparedRequest(request, config);
+ return apiResponseToMap(apiResponse);
+ }
+
+ private Map internalPut(
+ String endpoint,
+ Map requestBody,
+ Config config
+ ) {
+ HttpRequest request = prepareRequest(endpoint, config)
+ .PUT(HttpRequest.BodyPublishers.ofString(hashMapToJson(requestBody)))
+ .build();
+
+ String apiResponse = sendPreparedRequest(request, config);
+ return apiResponseToMap(apiResponse);
+ }
+
+ private String hashMapToJson(Map hash) {
+ return gson.toJson(hash);
+ }
+
+ private String objectToJson(Object obj) {
+ return gson.toJson(obj);
+ }
+
+ private String listToJson(List list) {
+ return gson.toJson(list);
+ }
+
+ private static Map apiResponseToMap(String apiResponse) {
+ return gson.fromJson(apiResponse, HashMap.class);
+ }
+
+ private static List> apiResponseToList(String apiResponse) {
+ List> newList = new ArrayList<>();
+ List apiResponseAsList = gson.fromJson(apiResponse, List.class);
+ for (Object k : apiResponseAsList) {
+ String temp = gson.toJson(k);
+ newList.add(gson.fromJson(temp, HashMap.class));
+ }
+ return newList;
+ }
+
+ private void checkResponseForErrors(
+ int statusCode,
+ String httpResponseBody,
+ String retryAfterHeader,
+ Config config
+ ) {
+ Map>> responseBody = apiResponseToMap(httpResponseBody);
+ Map error = responseBody.get("errors").get(0);
+ switch (statusCode) {
+ case 400:
+ case 500:
+ throw new ShipEngineException(
+ error.get("message"),
+ responseBody.get("request_id").toString(),
+ error.get("error_source"),
+ error.get("error_type"),
+ error.get("error_code")
+ );
+ case 404:
+ throw new ShipEngineException(
+ error.get("message"),
+ responseBody.get("request_id").toString(),
+ "shipengine",
+ error.get("error_type"),
+ error.get("error_code")
+ );
+ case 429:
+ int retry = Integer.parseInt(retryAfterHeader);
+ if (retry > config.getTimeout()) {
+ throw new ClientTimeoutError(
+ responseBody.get("request_id").toString(),
+ "shipengine",
+ retry
+ );
+ } else {
+ throw new RateLimitExceededException(
+ responseBody.get("request_id").toString(),
+ "shipengine",
+ retry
+ );
+ }
+ default:
+ return;
+ }
+ }
+}
diff --git a/src/main/java/com/shipengine/ShipEngine.java b/src/main/java/com/shipengine/ShipEngine.java
index 46eda09..5abff3e 100644
--- a/src/main/java/com/shipengine/ShipEngine.java
+++ b/src/main/java/com/shipengine/ShipEngine.java
@@ -1,45 +1,329 @@
package com.shipengine;
+import com.shipengine.exception.ShipEngineException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
public class ShipEngine {
- Config config;
+ private InternalClient client = new InternalClient();
+
+ private Config config;
public ShipEngine(String apiKey) {
this.config = new Config(apiKey);
}
+ public ShipEngine(String apiKey, int timeout, int retries, int pageSize) {
+ this.config = new Config(apiKey, timeout, retries, pageSize);
+ }
+
+ public ShipEngine(Map config) {
+ this.config = new Config(config);
+ }
+
public Config getConfig() {
return config;
}
- public String validateAddresses() {
- return config.getBaseUrl();
+ /**
+ * Address validation ensures accurate addresses and can lead to reduced shipping costs by preventing address
+ * correction surcharges. ShipEngine cross-references multiple databases to validate addresses and identify
+ * potential deliverability issues.
+ * See: https://shipengine.github.io/shipengine-openapi/#operation/validate_address
+ *
+ * @param address A list of HashMaps where each HashMap contains the address data to be validated.
+ * @return The response from ShipEngine API including the validated and normalized address.
+ */
+ public List> validateAddresses(List> address) {
+ List> apiResponse = new ArrayList<>();
+ try {
+ apiResponse = client.post(
+ "/v1/addresses/validate",
+ address,
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ /**
+ * Address validation ensures accurate addresses and can lead to reduced shipping costs by preventing address
+ * correction surcharges. ShipEngine cross-references multiple databases to validate addresses and identify
+ * potential deliverability issues.
+ * See: https://shipengine.github.io/shipengine-openapi/#operation/validate_address
+ *
+ * @param address A list of HashMaps where each HashMap contains the address data to be validated.
+ * @param config Method level configuration to set new values for properties of the
+ * global ShipEngineConfig object that will only affect the current request, not all requests.
+ * @return The response from ShipEngine API including the validated and normalized address.
+ */
+ public List> validateAddresses(List> address, Map config) {
+ Config mergedConfig = this.config.merge(config);
+ List> apiResponse = new ArrayList<>();
+ try {
+ apiResponse = client.post(
+ "/v1/addresses/validate",
+ address,
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map listCarriers() {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ "/v1/carriers",
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map listCarriers(Map config) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ "/v1/carriers",
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map createLabelFromShipmentDetails(Map shipment) {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.post(
+ "/v1/labels",
+ shipment,
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map createLabelFromShipmentDetails(Map shipment, Map config) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.post(
+ "/v1/labels",
+ shipment,
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map createLabelFromRateId(String rateId, Map params) {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.post(
+ String.format("/v1/labels/rates/%s", rateId),
+ params,
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map createLabelFromRateId(
+ String rateId,
+ Map params,
+ Map config
+ ) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.post(
+ String.format("/v1/labels/rates/%s", rateId),
+ params,
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
+ }
+
+ public Map getRatesWithShipmentDetails(Map shipment) {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.post(
+ "/v1/rates",
+ shipment,
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String listCarriers() {
- return config.getBaseUrl();
+ public Map getRatesWithShipmentDetails(Map shipment, Map config) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.post(
+ "/v1/rates",
+ shipment,
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String trackUsingCarrierCodeAndTrackingNumber() {
- return config.getBaseUrl();
+ public Map trackUsingCarrierCodeAndTrackingNumber(
+ Map trackingData
+ ) {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ String.format(
+ "/v1/tracking?carrier_code=%s&tracking_number=%s",
+ trackingData.get("carrierCode"),
+ trackingData.get("trackingNumber")
+ ),
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String trackUsingLabelId() {
- return config.getBaseUrl();
+ public Map trackUsingCarrierCodeAndTrackingNumber(
+ Map trackingData,
+ Map config
+ ) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ String.format(
+ "/v1/tracking?carrier_code=%s&tracking_number=%s",
+ trackingData.get("carrierCode"),
+ trackingData.get("trackingNumber")
+ ),
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String createLabelFromShipmentDetails() {
- return config.getBaseUrl();
+ public Map trackUsingLabelId(String labelId) {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ String.format(
+ "/v1/labels/%s/track",
+ labelId
+ ),
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String createLabelFromRate() {
- return config.getBaseUrl();
+ public Map trackUsingLabelId(String labelId, Map config) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ String.format(
+ "/v1/labels/%s/track",
+ labelId
+ ),
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String voidLabelWithLabelId() {
- return config.getBaseUrl();
+ /**
+ * Void label with a Label ID.
+ * See: https://shipengine.github.io/shipengine-openapi/#operation/void_label
+ *
+ * @param labelId The label_id of the label you wish to void.
+ * @return The response from ShipEngine API confirming the label was successfully voided or unable to be voided.
+ */
+ public Map voidLabelWithLabelId(String labelId) {
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ String.format("/v1/labels/%s/void", labelId),
+ this.getConfig()
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
- public String getRatesWithShipmentDetails() {
- return config.getBaseUrl();
+ /**
+ * Void label with a Label ID.
+ * See: https://shipengine.github.io/shipengine-openapi/#operation/void_label
+ *
+ * @param labelId The label_id of the label you wish to void.
+ * @param config Method level configuration to set new values for properties of the
+ * global ShipEngineConfig object that will only affect the current request, not all requests.
+ * @return The response from ShipEngine API confirming the label was successfully voided or unable to be voided.
+ */
+ public Map voidLabelWithLabelId(String labelId, Map config) {
+ Config mergedConfig = this.config.merge(config);
+ Map apiResponse = new HashMap<>();
+ try {
+ apiResponse = client.get(
+ String.format("/v1/labels/%s/void", labelId),
+ mergedConfig
+ );
+ return apiResponse;
+ } catch (ShipEngineException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return apiResponse;
}
}
diff --git a/src/main/java/com/shipengine/exception/ClientTimeoutError.java b/src/main/java/com/shipengine/exception/ClientTimeoutError.java
new file mode 100644
index 0000000..8e92269
--- /dev/null
+++ b/src/main/java/com/shipengine/exception/ClientTimeoutError.java
@@ -0,0 +1,49 @@
+package com.shipengine.exception;
+
+public class ClientTimeoutError extends ShipEngineException {
+
+ private int retryAfter;
+
+ private ErrorSource source;
+
+ private String requestID;
+
+ public int getRetryAfter() {
+ return retryAfter;
+ }
+
+ public void setRetryAfter(int retryAfter) {
+ this.retryAfter = retryAfter;
+ }
+
+ public ErrorSource getSource() {
+ return source;
+ }
+
+ public void setSource(ErrorSource source) {
+ this.source = source;
+ }
+
+ public String getRequestID() {
+ return requestID;
+ }
+
+ public void setRequestID(String requestID) {
+ this.requestID = requestID;
+ }
+
+ public ClientTimeoutError(
+ String requestID,
+ String source,
+ int retryAfter
+ ) {
+ super(
+ String.format("The request took longer than the %s seconds allowed.", retryAfter),
+ requestID,
+ ErrorSource.valueOf(source.toUpperCase()),
+ ErrorType.SYSTEM,
+ ErrorCode.TIMEOUT,
+ "https://www.shipengine.com/docs/rate-limits"
+ );
+ }
+}
diff --git a/src/main/java/com/shipengine/exception/FieldValueRequiredException.java b/src/main/java/com/shipengine/exception/FieldValueRequiredException.java
new file mode 100644
index 0000000..fd809c5
--- /dev/null
+++ b/src/main/java/com/shipengine/exception/FieldValueRequiredException.java
@@ -0,0 +1,26 @@
+package com.shipengine.exception;
+
+/**
+ * This error occurs when a required field has not been set. This includes fields
+ * that are conditionally required.
+ */
+public class FieldValueRequiredException extends RuntimeException {
+ /**
+ * The name of the invalid field.
+ */
+ private String fieldName;
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public void setFieldName(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public FieldValueRequiredException(
+ String fieldName
+ ) {
+ super(String.format("%s is a required field.", fieldName));
+ }
+}
diff --git a/src/main/java/com/shipengine/exception/InvalidFieldValueException.java b/src/main/java/com/shipengine/exception/InvalidFieldValueException.java
new file mode 100644
index 0000000..2f25b98
--- /dev/null
+++ b/src/main/java/com/shipengine/exception/InvalidFieldValueException.java
@@ -0,0 +1,48 @@
+package com.shipengine.exception;
+
+/**
+ * This error occurs when a field has been set to an invalid value.
+ */
+public class InvalidFieldValueException extends ShipEngineException {
+ /**
+ * The name of the invalid field.
+ */
+ private String fieldName;
+
+ /**
+ * The value of the invalid field.
+ */
+ private String fieldValue;
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ private void setFieldName(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public String getFieldValue() {
+ return fieldValue;
+ }
+
+ private void setFieldValue(String fieldValue) {
+ this.fieldValue = fieldValue;
+ }
+
+ public InvalidFieldValueException(
+ String fieldName,
+ String fieldValue
+ ) {
+ super(
+ String.format("%s - \"%s\" was provided.", fieldName, fieldValue),
+ "",
+ ErrorSource.SHIPENGINE,
+ ErrorType.VALIDATION,
+ ErrorCode.INVALID_FIELD_VALUE,
+ "https://www.shipengine.com/docs/"
+ );
+ this.setFieldName(fieldName);
+ this.setFieldValue(fieldValue);
+ }
+}
diff --git a/src/main/java/com/shipengine/exception/RateLimitExceededException.java b/src/main/java/com/shipengine/exception/RateLimitExceededException.java
new file mode 100644
index 0000000..276887a
--- /dev/null
+++ b/src/main/java/com/shipengine/exception/RateLimitExceededException.java
@@ -0,0 +1,34 @@
+package com.shipengine.exception;
+
+/**
+ * This error occurs when a request to ShipEngine API is blocked due to the rate
+ * limit being exceeded.
+ */
+public class RateLimitExceededException extends ShipEngineException {
+ /**
+ * The amount of time (in milliseconds) to wait before retrying the request.
+ */
+ private int retryAfter;
+
+ public int getRetryAfter() {
+ return retryAfter;
+ }
+
+ private void setRetryAfter(int retryAfter) {
+ this.retryAfter = retryAfter;
+ }
+
+ public RateLimitExceededException(
+ String requestID, String source, int retryAfter
+ ) {
+ super(
+ "You have exceeded the rate limit.",
+ requestID,
+ ErrorSource.valueOf(source.toUpperCase()),
+ ErrorType.SYSTEM,
+ ErrorCode.RATE_LIMIT_EXCEEDED,
+ "https://www.shipengine.com/docs/rate-limits"
+ );
+ this.setRetryAfter(retryAfter);
+ }
+}
diff --git a/src/main/java/com/shipengine/exception/ShipEngineException.java b/src/main/java/com/shipengine/exception/ShipEngineException.java
new file mode 100644
index 0000000..b2ca0ef
--- /dev/null
+++ b/src/main/java/com/shipengine/exception/ShipEngineException.java
@@ -0,0 +1,224 @@
+package com.shipengine.exception;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashSet;
+
+/**
+ * An error thrown by the ShipEngine SDK. All other SDK errors inherit from this
+ * class.
+ */
+public class ShipEngineException extends RuntimeException {
+ public static HashSet getEnums() {
+ HashSet values = new HashSet<>();
+
+ for (ErrorSource k : ErrorSource.values()) {
+ values.add(k.name());
+ }
+
+ for (ErrorType p : ErrorType.values()) {
+ values.add(p.name());
+ }
+
+ for (ErrorCode c : ErrorCode.values()) {
+ values.add(c.name());
+ }
+
+ return values;
+ }
+
+ enum ErrorSource {
+ CARRIER,
+ ORDER_SOURCE,
+ SHIPENGINE
+ }
+
+ enum ErrorType {
+ ACCOUNT_STATUS,
+ AUTHORIZATION,
+ BUSINESS_RULES,
+ ERROR,
+ SECURITY,
+ SYSTEM,
+ VALIDATION
+ }
+
+ enum ErrorCode {
+ ADDRESS_NOT_FOUND,
+ AUTO_FUND_NOT_SUPPORTED,
+ BATCH_CANNOT_BE_MODIFIED,
+ CARRIER_CONFLICT,
+ CARRIER_NOT_CONNECTED,
+ CARRIER_NOT_SUPPORTED,
+ CONFIRMATION_NOT_SUPPORTED,
+ FIELD_CONFLICT,
+ FIELD_VALUE_REQUIRED,
+ FORBIDDEN,
+ IDENTIFIER_CONFLICT,
+ IDENTIFIER_MUST_MATCH,
+ INCOMPATIBLE_PAIRED_LABELS,
+ INVALID_ADDRESS,
+ INVALID_BILLING_PLAN,
+ INVALID_CHARGE_EVENT,
+ INVALID_FIELD_VALUE,
+ INVALID_IDENTIFIER,
+ INVALID_STATUS,
+ INVALID_STRING_LENGTH,
+ LABEL_IMAGES_NOT_SUPPORTED,
+ METER_FAILURE,
+ MINIMUM_POSTAL_CODE_VERIFICATION_FAILED,
+ NOT_FOUND,
+ PARTIALLY_VERIFIED_TO_PREMISE_LEVEL,
+ RATE_LIMIT_EXCEEDED,
+ REQUEST_BODY_REQUIRED,
+ RETURN_LABEL_NOT_SUPPORTED,
+ SUBSCRIPTION_INACTIVE,
+ TERMS_NOT_ACCEPTED,
+ TIMEOUT,
+ TRACKING_NOT_SUPPORTED,
+ TRIAL_EXPIRED,
+ UNAUTHORIZED,
+ UNSPECIFIED,
+ VERIFICATION_CONFLICT,
+ WAREHOUSE_CONFLICT,
+ WEBHOOK_EVENT_TYPE_CONFLICT
+ }
+
+ /**
+ * If the error came from the ShipEngine server (as opposed to a client-side
+ * error) then this is the unique ID of the HTTP request that returned the
+ * error. You can use this ID when contacting ShipEngine support for help.
+ */
+ private String requestID;
+
+ /**
+ * Indicates where the error originated. This lets you know whether you should
+ * contact ShipEngine for support or if you should contact the carrier or
+ * marketplace instead.
+ *
+ * @see ...
+ */
+ private ErrorSource source;
+
+ /**
+ * Indicates the type of error that occurred, such as a validation error, a
+ * security error, etc.
+ *
+ * @see ...
+ */
+ private ErrorType type;
+
+ /**
+ * A code that indicates the specific error that occurred, such as missing a
+ * required field, an invalid address, a timeout, etc.
+ *
+ * @see ...
+ */
+ private ErrorCode code;
+
+ /**
+ * Some errors include a URL that you can visit to learn more about the error,
+ * find out how to resolve it, or get support.
+ */
+ private URL url;
+
+ public String getRequestID() {
+ return requestID;
+ }
+
+ public ErrorSource getSource() {
+ return source;
+ }
+
+ public ErrorType getType() {
+ return type;
+ }
+
+ public ErrorCode getCode() {
+ return code;
+ }
+
+ public URL getUrl() {
+ return url;
+ }
+
+ public void setRequestID(String requestID) {
+ this.requestID = requestID;
+ }
+
+ public void setSource(ErrorSource source) {
+ this.source = source;
+ }
+
+ public void setType(ErrorType type) {
+ this.type = type;
+ }
+
+ public void setCode(ErrorCode code) {
+ this.code = code;
+ }
+
+ public void setUrl(String url) {
+ try {
+ this.url = new URL(url);
+ } catch (MalformedURLException err) {
+ err.printStackTrace();
+ }
+ }
+
+ public ShipEngineException(
+ String message,
+ String requestID,
+ ErrorSource source,
+ ErrorType type,
+ ErrorCode code,
+ String url
+ ) {
+ super(message);
+ this.setRequestID(requestID);
+ this.setSource(source);
+ this.setType(type);
+ this.setCode(code);
+ this.setUrl(url);
+ }
+
+ public ShipEngineException(
+ String message,
+ ErrorSource source,
+ ErrorType type,
+ ErrorCode code,
+ String url
+ ) {
+ super(message);
+ this.setSource(source);
+ this.setType(type);
+ this.setCode(code);
+ this.setUrl(url);
+ }
+
+ public ShipEngineException(
+ String message,
+ String requestID,
+ String source,
+ String type,
+ String code
+ ) {
+ super(message);
+ this.setRequestID(requestID);
+ this.setSource(ErrorSource.valueOf(source.toUpperCase()));
+ this.setType(ErrorType.valueOf(type.toUpperCase()));
+ this.setCode(ErrorCode.valueOf(code));
+ }
+
+ public ShipEngineException(
+ String message,
+ String source,
+ String type,
+ String code
+ ) {
+ super(message);
+ this.setSource(ErrorSource.valueOf(source.toUpperCase()));
+ this.setType(ErrorType.valueOf(type.toUpperCase()));
+ this.setCode(ErrorCode.valueOf(code));
+ }
+}
diff --git a/src/main/java/com/shipengine/exception/ValidationException.java b/src/main/java/com/shipengine/exception/ValidationException.java
new file mode 100644
index 0000000..f3c50e6
--- /dev/null
+++ b/src/main/java/com/shipengine/exception/ValidationException.java
@@ -0,0 +1,24 @@
+package com.shipengine.exception;
+
+public class ValidationException extends ShipEngineException {
+
+ public ValidationException(
+ String message,
+ String requestID,
+ ErrorSource source,
+ ErrorType type,
+ ErrorCode code,
+ String url
+ ) {
+ super(message, requestID, source, type, code, url);
+ }
+
+ public ValidationException(
+ String message,
+ String source,
+ String type,
+ String code
+ ) {
+ super(message, source, type, code);
+ }
+}
diff --git a/src/main/java/com/shipengine/util/Constants.java b/src/main/java/com/shipengine/util/Constants.java
new file mode 100644
index 0000000..ac1124d
--- /dev/null
+++ b/src/main/java/com/shipengine/util/Constants.java
@@ -0,0 +1,7 @@
+package com.shipengine.util;
+
+public interface Constants {
+ String API_KEY = "TEST_vMiVbICUjBz4BZjq0TRBLC/9MrxY4+yjvb1G1RMxlJs";
+ String BASE_URL = "https://api.shipengine.com/";
+ String TEST_URL = "http://127.0.0.1:1080/";
+}
diff --git a/src/test/java/com/shipengine/ConfigTest.java b/src/test/java/com/shipengine/ConfigTest.java
index 805fdff..d70d5ac 100644
--- a/src/test/java/com/shipengine/ConfigTest.java
+++ b/src/test/java/com/shipengine/ConfigTest.java
@@ -1,20 +1,48 @@
package com.shipengine;
-import static org.junit.Assert.assertEquals;
-
+import com.shipengine.util.Constants;
import org.junit.Test;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
/**
* Unit test for Config.
*/
public class ConfigTest {
- /**
- * Should allow the config to just be the API key
- */
- @Test
- public void shouldAllowApiKeyOnly() {
- ShipEngine client = new ShipEngine("test");
-
- assertEquals(client.getConfig().getApiKey(), "test");
- }
+ /**
+ * Should allow the config to just be the API key
+ */
+ @Test
+ public void shouldAllowApiKeyOnlyConstructor() {
+ ShipEngine client = new ShipEngine(Constants.API_KEY);
+
+ assertEquals(Constants.API_KEY, client.getConfig().getApiKey());
+ }
+
+ /**
+ * Should return the Global Config object when calling the merge() method
+ * with no arguments.
+ */
+ @Test
+ public void shouldReturnGlobalConfigFromEmptyMergeCall() {
+ ShipEngine client = new ShipEngine(Constants.API_KEY);
+
+ assertEquals(Config.class, client.getConfig().merge().getClass());
+ }
+
+ /**
+ * Should allow method level configuration.
+ */
+ @Test
+ public void shouldAllowMethodLevelConfig() {
+ ShipEngine client = new ShipEngine(Constants.API_KEY);
+
+ Map newConfig = new HashMap<>();
+ newConfig.put("retries", 3);
+ Map result = client.listCarriers(newConfig);
+ assertEquals(result.getClass(), HashMap.class);
+ }
}
diff --git a/src/test/java/com/shipengine/ShipEngineTest.java b/src/test/java/com/shipengine/ShipEngineTest.java
index 5b3d0a9..0177a44 100644
--- a/src/test/java/com/shipengine/ShipEngineTest.java
+++ b/src/test/java/com/shipengine/ShipEngineTest.java
@@ -1,18 +1,1431 @@
package com.shipengine;
-import static org.junit.Assert.assertEquals;
-
+import com.shipengine.util.Constants;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
+import org.mockserver.client.server.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.matchers.Times;
+import org.mockserver.model.Header;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
/**
* Unit test for simple App.
*/
public class ShipEngineTest {
+ private ClientAndServer mockServer;
+
+ private final HashMap customConfig = new HashMap<>() {{
+ put("apiKey", Constants.API_KEY);
+ put("baseUrl", Constants.TEST_URL);
+ put("retries", 3);
+ }};
+
+ @Before
+ public void startServer() {
+ mockServer = startClientAndServer(1080);
+ }
+
+ @After
+ public void stopMockServer() {
+ mockServer.stop();
+ }
+
/**
- * Rigorous Test :-)
+ * Testing Address Validation with a valid address.
*/
@Test
- public void shouldAnswerWithTrue() {
- assertEquals(new ShipEngine("test").validateAddresses(), "https://api.shipengine.com/");
+ public void successfulAddressValidation() {
+ new MockServerClient("127.0.0.1", 1080)
+ .when(request()
+ .withMethod("POST")
+ .withPath("/v1/addresses/validate"),
+ Times.exactly(1))
+ .respond(response()
+ .withStatusCode(200)
+ .withBody("[\n" +
+ " {\n" +
+ " \"status\": \"verified\",\n" +
+ " \"original_address\": {\n" +
+ " \"name\": \"ShipEngine\",\n" +
+ " \"phone\": \"1-123-456-7891\",\n" +
+ " \"company_name\": null,\n" +
+ " \"address_line1\": \"3800 N Lamar Blvd\",\n" +
+ " \"address_line2\": \"ste 220\",\n" +
+ " \"address_line3\": null,\n" +
+ " \"city_locality\": \"Austin\",\n" +
+ " \"state_province\": \"TX\",\n" +
+ " \"postal_code\": \"78756\",\n" +
+ " \"country_code\": \"US\",\n" +
+ " \"address_residential_indicator\": \"unknown\"\n" +
+ " },\n" +
+ " \"matched_address\": {\n" +
+ " \"name\": \"SHIPENGINE\",\n" +
+ " \"phone\": \"1-123-456-7891\",\n" +
+ " \"company_name\": null,\n" +
+ " \"address_line1\": \"3800 N LAMAR BLVD STE 220\",\n" +
+ " \"address_line2\": \"\",\n" +
+ " \"address_line3\": null,\n" +
+ " \"city_locality\": \"AUSTIN\",\n" +
+ " \"state_province\": \"TX\",\n" +
+ " \"postal_code\": \"78756-0003\",\n" +
+ " \"country_code\": \"US\",\n" +
+ " \"address_residential_indicator\": \"no\"\n" +
+ " },\n" +
+ " \"messages\": []\n" +
+ " }\n" +
+ "]")
+ .withDelay(TimeUnit.SECONDS, 1));
+
+ HashMap stubAddress = new HashMap<>() {{
+ put("name", "ShipEngine");
+ put("company", "Auctane");
+ put("phone", "1-123-456-7891");
+ put("address_line1", "3800 N Lamar Blvd");
+ put("address_line2", "ste 220");
+ put("city_locality", "Austin");
+ put("state_province", "TX");
+ put("postal_code", "78756");
+ put("country_code", "US");
+ put("address_residential_indicator", "unknown");
+ }};
+
+ List> unvalidatedAddress = List.of(stubAddress);
+ List> validatedAddress = new ShipEngine(customConfig).validateAddresses(unvalidatedAddress);
+ assertEquals("verified", validatedAddress.get(0).get("status"));
+ }
+
+ /**
+ * Testing successful call to listCarriers which fetches all
+ * carrier accounts connected o given ShipEngine Account.
+ */
+ @Test
+ public void successfulListCarriers() {
+ new MockServerClient("127.0.0.1", 1080)
+ .when(request()
+ .withMethod("GET")
+ .withPath("/v1/carriers"),
+ Times.exactly(1))
+ .respond(response()
+ .withStatusCode(200)
+ .withBody("{\n" +
+ " \"carriers\": [\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"account_number\": \"test_account_656171\",\n" +
+ " \"requires_funded_amount\": true,\n" +
+ " \"balance\": 8948.3400,\n" +
+ " \"nickname\": \"ShipEngine Test Account - Stamps.com\",\n" +
+ " \"friendly_name\": \"Stamps.com\",\n" +
+ " \"primary\": false,\n" +
+ " \"has_multi_package_supporting_services\": false,\n" +
+ " \"supports_label_messages\": true,\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_first_class_mail\",\n" +
+ " \"name\": \"USPS First Class Mail\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_media_mail\",\n" +
+ " \"name\": \"USPS Media Mail\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_parcel_select\",\n" +
+ " \"name\": \"USPS Parcel Select Ground\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_priority_mail\",\n" +
+ " \"name\": \"USPS Priority Mail\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_priority_mail_express\",\n" +
+ " \"name\": \"USPS Priority Mail Express\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_first_class_mail_international\",\n" +
+ " \"name\": \"USPS First Class Mail Intl\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_priority_mail_international\",\n" +
+ " \"name\": \"USPS Priority Mail Intl\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"service_code\": \"usps_priority_mail_express_international\",\n" +
+ " \"name\": \"USPS Priority Mail Express Intl\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " }\n" +
+ " ],\n" +
+ " \"packages\": [\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"cubic\",\n" +
+ " \"name\": \"Cubic\",\n" +
+ " \"description\": \"Cubic\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"flat_rate_envelope\",\n" +
+ " \"name\": \"Flat Rate Envelope\",\n" +
+ " \"description\": \"USPS flat rate envelope. A special cardboard envelope provided by the USPS that clearly indicates \\\"Flat Rate\\\".\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"flat_rate_legal_envelope\",\n" +
+ " \"name\": \"Flat Rate Legal Envelope\",\n" +
+ " \"description\": \"Flat Rate Legal Envelope\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"flat_rate_padded_envelope\",\n" +
+ " \"name\": \"Flat Rate Padded Envelope\",\n" +
+ " \"description\": \"Flat Rate Padded Envelope\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"large_envelope_or_flat\",\n" +
+ " \"name\": \"Large Envelope or Flat\",\n" +
+ " \"description\": \"Large envelope or flat. Has one dimension that is between 11 1/2\\\" and 15\\\" long, 6 1/18\\\" and 12\\\" high, or 1/4\\\" and 3/4\\\" thick.\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"large_flat_rate_box\",\n" +
+ " \"name\": \"Large Flat Rate Box\",\n" +
+ " \"description\": \"Large Flat Rate Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"large_package\",\n" +
+ " \"name\": \"Large Package (any side \\u003e 12\\\")\",\n" +
+ " \"description\": \"Large package. Longest side plus the distance around the thickest part is over 84\\\" and less than or equal to 108\\\".\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"letter\",\n" +
+ " \"name\": \"Letter\",\n" +
+ " \"description\": \"Letter\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"medium_flat_rate_box\",\n" +
+ " \"name\": \"Medium Flat Rate Box\",\n" +
+ " \"description\": \"USPS flat rate box. A special 11\\\" x 8 1/2\\\" x 5 1/2\\\" or 14\\\" x 3.5\\\" x 12\\\" USPS box that clearly indicates \\\"Flat Rate Box\\\"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"non_rectangular\",\n" +
+ " \"name\": \"Non Rectangular Package\",\n" +
+ " \"description\": \"Non-Rectangular package type that is cylindrical in shape.\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"name\": \"Package\",\n" +
+ " \"description\": \"Package. Longest side plus the distance around the thickest part is less than or equal to 84\\\"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"regional_rate_box_a\",\n" +
+ " \"name\": \"Regional Rate Box A\",\n" +
+ " \"description\": \"Regional Rate Box A\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"regional_rate_box_b\",\n" +
+ " \"name\": \"Regional Rate Box B\",\n" +
+ " \"description\": \"Regional Rate Box B\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"small_flat_rate_box\",\n" +
+ " \"name\": \"Small Flat Rate Box\",\n" +
+ " \"description\": \"Small Flat Rate Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"thick_envelope\",\n" +
+ " \"name\": \"Thick Envelope\",\n" +
+ " \"description\": \"Thick envelope. Envelopes or flats greater than 3/4\\\" at the thickest point.\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"options\": [\n" +
+ " {\n" +
+ " \"name\": \"non_machinable\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_account\",\n" +
+ " \"default_value\": null,\n" +
+ " \"description\": \"Bill To Account\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_party\",\n" +
+ " \"default_value\": null,\n" +
+ " \"description\": \"Bill To Party\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_postal_code\",\n" +
+ " \"default_value\": null,\n" +
+ " \"description\": \"Bill To Postal Code\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_country_code\",\n" +
+ " \"default_value\": null,\n" +
+ " \"description\": \"Bill To Country Code\"\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"account_number\": \"test_account_656172\",\n" +
+ " \"requires_funded_amount\": false,\n" +
+ " \"balance\": 0.0,\n" +
+ " \"nickname\": \"ShipEngine Test Account - UPS\",\n" +
+ " \"friendly_name\": \"UPS\",\n" +
+ " \"primary\": false,\n" +
+ " \"has_multi_package_supporting_services\": true,\n" +
+ " \"supports_label_messages\": true,\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_standard_international\",\n" +
+ " \"name\": \"UPS Standard®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_next_day_air_early_am\",\n" +
+ " \"name\": \"UPS Next Day Air® Early\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_worldwide_express\",\n" +
+ " \"name\": \"UPS Worldwide Express®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_next_day_air\",\n" +
+ " \"name\": \"UPS Next Day Air®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_ground_international\",\n" +
+ " \"name\": \"UPS Ground® (International)\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_worldwide_express_plus\",\n" +
+ " \"name\": \"UPS Worldwide Express Plus®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_next_day_air_saver\",\n" +
+ " \"name\": \"UPS Next Day Air Saver®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_worldwide_expedited\",\n" +
+ " \"name\": \"UPS Worldwide Expedited®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_2nd_day_air_am\",\n" +
+ " \"name\": \"UPS 2nd Day Air AM®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_2nd_day_air\",\n" +
+ " \"name\": \"UPS 2nd Day Air®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_worldwide_saver\",\n" +
+ " \"name\": \"UPS Worldwide Saver®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_2nd_day_air_international\",\n" +
+ " \"name\": \"UPS 2nd Day Air® (International)\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_3_day_select\",\n" +
+ " \"name\": \"UPS 3 Day Select®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_ground\",\n" +
+ " \"name\": \"UPS® Ground\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656172\",\n" +
+ " \"carrier_code\": \"ups\",\n" +
+ " \"service_code\": \"ups_next_day_air_international\",\n" +
+ " \"name\": \"UPS Next Day Air® (International)\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " }\n" +
+ " ],\n" +
+ " \"packages\": [\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"name\": \"Package\",\n" +
+ " \"description\": \"Package. Longest side plus the distance around the thickest part is less than or equal to 84\\\"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups__express_box_large\",\n" +
+ " \"name\": \"UPS Express® Box - Large\",\n" +
+ " \"description\": \"Express Box - Large\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_10_kg_box\",\n" +
+ " \"name\": \"UPS 10 KG Box®\",\n" +
+ " \"description\": \"10 KG Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_25_kg_box\",\n" +
+ " \"name\": \"UPS 25 KG Box®\",\n" +
+ " \"description\": \"25 KG Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_express_box\",\n" +
+ " \"name\": \"UPS Express® Box\",\n" +
+ " \"description\": \"Express Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_express_box_medium\",\n" +
+ " \"name\": \"UPS Express® Box - Medium\",\n" +
+ " \"description\": \"Express Box - Medium\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_express_box_small\",\n" +
+ " \"name\": \"UPS Express® Box - Small\",\n" +
+ " \"description\": \"Express Box - Small\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_express_pak\",\n" +
+ " \"name\": \"UPS Express® Pak\",\n" +
+ " \"description\": \"Pak\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_letter\",\n" +
+ " \"name\": \"UPS Letter\",\n" +
+ " \"description\": \"Letter\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"ups_tube\",\n" +
+ " \"name\": \"UPS Tube\",\n" +
+ " \"description\": \"Tube\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"options\": [\n" +
+ " {\n" +
+ " \"name\": \"bill_to_account\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_country_code\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_party\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_postal_code\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"collect_on_delivery\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"contains_alcohol\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"delivered_duty_paid\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"dry_ice\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"dry_ice_weight\",\n" +
+ " \"default_value\": \"0\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"freight_class\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"non_machinable\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"saturday_delivery\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"shipper_release\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"Driver may release package without signature\"\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"account_number\": \"test_account_656173\",\n" +
+ " \"requires_funded_amount\": false,\n" +
+ " \"balance\": 0.0,\n" +
+ " \"nickname\": \"ShipEngine Test Account - FedEx\",\n" +
+ " \"friendly_name\": \"FedEx\",\n" +
+ " \"primary\": false,\n" +
+ " \"has_multi_package_supporting_services\": true,\n" +
+ " \"supports_label_messages\": true,\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_ground\",\n" +
+ " \"name\": \"FedEx Ground®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_home_delivery\",\n" +
+ " \"name\": \"FedEx Home Delivery®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_2day\",\n" +
+ " \"name\": \"FedEx 2Day®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_2day_am\",\n" +
+ " \"name\": \"FedEx 2Day® A.M.\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_express_saver\",\n" +
+ " \"name\": \"FedEx Express Saver®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_standard_overnight\",\n" +
+ " \"name\": \"FedEx Standard Overnight®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_priority_overnight\",\n" +
+ " \"name\": \"FedEx Priority Overnight®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_first_overnight\",\n" +
+ " \"name\": \"FedEx First Overnight®\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_1_day_freight\",\n" +
+ " \"name\": \"FedEx 1Day® Freight\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_2_day_freight\",\n" +
+ " \"name\": \"FedEx 2Day® Freight\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_3_day_freight\",\n" +
+ " \"name\": \"FedEx 3Day® Freight\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_first_overnight_freight\",\n" +
+ " \"name\": \"FedEx First Overnight® Freight\",\n" +
+ " \"domestic\": true,\n" +
+ " \"international\": false,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_ground_international\",\n" +
+ " \"name\": \"FedEx International Ground®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_international_economy\",\n" +
+ " \"name\": \"FedEx International Economy®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_international_priority\",\n" +
+ " \"name\": \"FedEx International Priority®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_international_first\",\n" +
+ " \"name\": \"FedEx International First®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_international_economy_freight\",\n" +
+ " \"name\": \"FedEx International Economy® Freight\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_international_priority_freight\",\n" +
+ " \"name\": \"FedEx International Priority® Freight\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": true\n" +
+ " },\n" +
+ " {\n" +
+ " \"carrier_id\": \"se-656173\",\n" +
+ " \"carrier_code\": \"fedex\",\n" +
+ " \"service_code\": \"fedex_international_connect_plus\",\n" +
+ " \"name\": \"FedEx International Connect Plus®\",\n" +
+ " \"domestic\": false,\n" +
+ " \"international\": true,\n" +
+ " \"is_multi_package_supported\": false\n" +
+ " }\n" +
+ " ],\n" +
+ " \"packages\": [\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_envelope_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Envelope\",\n" +
+ " \"description\": \"FedEx® Envelope\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_extra_large_box_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Extra Large Box\",\n" +
+ " \"description\": \"FedEx® Extra Large Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_large_box_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Large Box\",\n" +
+ " \"description\": \"FedEx® Large Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_medium_box_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Medium Box\",\n" +
+ " \"description\": \"FedEx® Medium Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_pak_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Pak\",\n" +
+ " \"description\": \"FedEx® Pak\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_small_box_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Small Box\",\n" +
+ " \"description\": \"FedEx® Small Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_tube_onerate\",\n" +
+ " \"name\": \"FedEx One Rate® Tube\",\n" +
+ " \"description\": \"FedEx® Tube\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_10kg_box\",\n" +
+ " \"name\": \"FedEx® 10kg Box\",\n" +
+ " \"description\": \"FedEx® 10kg Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_25kg_box\",\n" +
+ " \"name\": \"FedEx® 25kg Box\",\n" +
+ " \"description\": \"FedEx® 25kg Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_box\",\n" +
+ " \"name\": \"FedEx® Box\",\n" +
+ " \"description\": \"FedEx® Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_envelope\",\n" +
+ " \"name\": \"FedEx® Envelope\",\n" +
+ " \"description\": \"FedEx® Envelope\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_extra_large_box\",\n" +
+ " \"name\": \"FedEx® Extra Large Box\",\n" +
+ " \"description\": \"FedEx® Extra Large Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_large_box\",\n" +
+ " \"name\": \"FedEx® Large Box\",\n" +
+ " \"description\": \"FedEx® Large Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_medium_box\",\n" +
+ " \"name\": \"FedEx® Medium Box\",\n" +
+ " \"description\": \"FedEx® Medium Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_pak\",\n" +
+ " \"name\": \"FedEx® Pak\",\n" +
+ " \"description\": \"FedEx® Pak\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_small_box\",\n" +
+ " \"name\": \"FedEx® Small Box\",\n" +
+ " \"description\": \"FedEx® Small Box\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"fedex_tube\",\n" +
+ " \"name\": \"FedEx® Tube\",\n" +
+ " \"description\": \"FedEx® Tube\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"package_id\": null,\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"name\": \"Package\",\n" +
+ " \"description\": \"Package. Longest side plus the distance around the thickest part is less than or equal to 84\\\"\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"options\": [\n" +
+ " {\n" +
+ " \"name\": \"bill_to_account\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_country_code\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_party\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"bill_to_postal_code\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"collect_on_delivery\",\n" +
+ " \"default_value\": \"\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"contains_alcohol\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"delivered_duty_paid\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"dry_ice\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"dry_ice_weight\",\n" +
+ " \"default_value\": \"0\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"non_machinable\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"saturday_delivery\",\n" +
+ " \"default_value\": \"false\",\n" +
+ " \"description\": \"\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ],\n" +
+ " \"request_id\": \"81a0e0f0-4fed-4b6b-8965-56e1b82baad2\",\n" +
+ " \"errors\": []\n" +
+ "}")
+ .withDelay(TimeUnit.SECONDS, 1));
+
+ Map listOfCarriers = new ShipEngine(customConfig).listCarriers();
+// assertEquals(List.class, listOfCarriers.getClass());
+ assertEquals(HashMap.class, listOfCarriers.getClass());
+ }
+
+ @Test
+ public void successfulCreateLabelUsingShipmentDetails() {
+ new MockServerClient("127.0.0.1", 1080)
+ .when(request()
+ .withMethod("POST")
+ .withPath("/v1/labels"),
+ Times.exactly(1))
+ .respond(response()
+ .withStatusCode(200)
+ .withBody("{\n" +
+ " \"batch_id\": \"\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"charge_event\": \"carrier_default\",\n" +
+ " \"created_at\": \"2021-08-05T16:47:47.8768838Z\",\n" +
+ " \"display_scheme\": \"label\",\n" +
+ " \"form_download\": null,\n" +
+ " \"insurance_claim\": null,\n" +
+ " \"insurance_cost\": {\n" +
+ " \"amount\": 0.0,\n" +
+ " \"currency\": \"usd\"\n" +
+ " },\n" +
+ " \"is_international\": false,\n" +
+ " \"is_return_label\": false,\n" +
+ " \"label_download\": {\n" +
+ " \"href\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.pdf\",\n" +
+ " \"pdf\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.pdf\",\n" +
+ " \"png\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.png\",\n" +
+ " \"zpl\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.zpl\"\n" +
+ " },\n" +
+ " \"label_format\": \"pdf\",\n" +
+ " \"label_id\": \"se-799373193\",\n" +
+ " \"label_image_id\": null,\n" +
+ " \"label_layout\": \"4x6\",\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"packages\": [\n" +
+ " {\n" +
+ " \"dimensions\": {\n" +
+ " \"height\": 0.0,\n" +
+ " \"length\": 0.0,\n" +
+ " \"unit\": \"inch\",\n" +
+ " \"width\": 0.0\n" +
+ " },\n" +
+ " \"external_package_id\": null,\n" +
+ " \"insured_value\": {\n" +
+ " \"amount\": 0.0,\n" +
+ " \"currency\": \"usd\"\n" +
+ " },\n" +
+ " \"label_messages\": {\n" +
+ " \"reference1\": null,\n" +
+ " \"reference2\": null,\n" +
+ " \"reference3\": null\n" +
+ " },\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"package_id\": 80328023,\n" +
+ " \"sequence\": 1,\n" +
+ " \"tracking_number\": \"9400111899560334651289\",\n" +
+ " \"weight\": {\n" +
+ " \"unit\": \"ounce\",\n" +
+ " \"value\": 1.0\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"rma_number\": null,\n" +
+ " \"service_code\": \"usps_first_class_mail\",\n" +
+ " \"ship_date\": \"2021-08-05T00:00:00Z\",\n" +
+ " \"shipment_cost\": {\n" +
+ " \"amount\": 3.35,\n" +
+ " \"currency\": \"usd\"\n" +
+ " },\n" +
+ " \"shipment_id\": \"se-144794216\",\n" +
+ " \"status\": \"completed\",\n" +
+ " \"trackable\": true,\n" +
+ " \"tracking_number\": \"9400111899560334651289\",\n" +
+ " \"tracking_status\": \"in_transit\",\n" +
+ " \"voided\": false,\n" +
+ " \"voided_at\": null\n" +
+ "}")
+ .withDelay(TimeUnit.SECONDS, 1));
+
+ Map shipmentDetails = new HashMap<>() {{
+ put("shipment", new HashMap<>() {{
+ put("carrier_id", "se-1234");
+ put("service_code", "usps_first_class_mail");
+ put("external_order_id", "string");
+ put("items", new ArrayList<>());
+ put("tax_identifiers", new ArrayList<>() {{
+ add(new HashMap<>() {{
+ put("taxable_entity_type", "shipper");
+ put("identifier_type", "vat");
+ put("issuing_authority", "string");
+ put("value", "string");
+ }});
+ }});
+ put("external_shipment_id", "string");
+ put("ship_date", "2018-09-23T00:00:00.000Z");
+ put("ship_to", new HashMap<>() {{
+ put("name", "John Doe");
+ put("phone", "1-123-456-7894");
+ put("company_name", "The Home Depot");
+ put("address_line1", "1999 Bishop Grandin Blvd.");
+ put("address_line2", "Unit 408");
+ put("address_line3", "Building #7");
+ put("city_locality", "Winnipeg");
+ put("state_province", "Manitoba");
+ put("postal_code", "78756");
+ put("country_code", "CA");
+ put("address_residential_indicator", "no");
+ }});
+ put("ship_from", new HashMap<>() {{
+ put("name", "John Doe");
+ put("phone", "1-123-456-7894");
+ put("company_name", "The Home Depot");
+ put("address_line1", "1999 Bishop Grandin Blvd.");
+ put("address_line2", "Unit 408");
+ put("address_line3", "Building #7");
+ put("city_locality", "Winnipeg");
+ put("state_province", "Manitoba");
+ put("postal_code", "78756");
+ put("country_code", "CA");
+ put("address_residential_indicator", "no");
+ }});
+ }});
+ }};
+
+ Map labelData = new ShipEngine(customConfig).createLabelFromShipmentDetails(shipmentDetails);
+ assertEquals("stamps_com", labelData.get("carrier_code"));
+ }
+
+ @Test
+ public void successfulCreateLabelUsingLabelId() {
+ new MockServerClient("127.0.0.1", 1080)
+ .when(request()
+ .withMethod("POST")
+ .withPath("/v1/labels/rates/se-1234"),
+ Times.exactly(1))
+ .respond(response()
+ .withStatusCode(200)
+ .withBody("{\n" +
+ " \"batch_id\": \"\",\n" +
+ " \"carrier_code\": \"stamps_com\",\n" +
+ " \"carrier_id\": \"se-656171\",\n" +
+ " \"charge_event\": \"carrier_default\",\n" +
+ " \"created_at\": \"2021-08-05T16:47:47.8768838Z\",\n" +
+ " \"display_scheme\": \"label\",\n" +
+ " \"form_download\": null,\n" +
+ " \"insurance_claim\": null,\n" +
+ " \"insurance_cost\": {\n" +
+ " \"amount\": 0.0,\n" +
+ " \"currency\": \"usd\"\n" +
+ " },\n" +
+ " \"is_international\": false,\n" +
+ " \"is_return_label\": false,\n" +
+ " \"label_download\": {\n" +
+ " \"href\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.pdf\",\n" +
+ " \"pdf\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.pdf\",\n" +
+ " \"png\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.png\",\n" +
+ " \"zpl\": \"https://api.shipengine.com/v1/downloads/10/_EKGeA4yuEuLzLq81iOzew/label-75693596.zpl\"\n" +
+ " },\n" +
+ " \"label_format\": \"pdf\",\n" +
+ " \"label_id\": \"se-799373193\",\n" +
+ " \"label_image_id\": null,\n" +
+ " \"label_layout\": \"4x6\",\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"packages\": [\n" +
+ " {\n" +
+ " \"dimensions\": {\n" +
+ " \"height\": 0.0,\n" +
+ " \"length\": 0.0,\n" +
+ " \"unit\": \"inch\",\n" +
+ " \"width\": 0.0\n" +
+ " },\n" +
+ " \"external_package_id\": null,\n" +
+ " \"insured_value\": {\n" +
+ " \"amount\": 0.0,\n" +
+ " \"currency\": \"usd\"\n" +
+ " },\n" +
+ " \"label_messages\": {\n" +
+ " \"reference1\": null,\n" +
+ " \"reference2\": null,\n" +
+ " \"reference3\": null\n" +
+ " },\n" +
+ " \"package_code\": \"package\",\n" +
+ " \"package_id\": 80328023,\n" +
+ " \"sequence\": 1,\n" +
+ " \"tracking_number\": \"9400111899560334651289\",\n" +
+ " \"weight\": {\n" +
+ " \"unit\": \"ounce\",\n" +
+ " \"value\": 1.0\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"rma_number\": null,\n" +
+ " \"service_code\": \"usps_first_class_mail\",\n" +
+ " \"ship_date\": \"2021-08-05T00:00:00Z\",\n" +
+ " \"shipment_cost\": {\n" +
+ " \"amount\": 3.35,\n" +
+ " \"currency\": \"usd\"\n" +
+ " },\n" +
+ " \"shipment_id\": \"se-144794216\",\n" +
+ " \"status\": \"completed\",\n" +
+ " \"trackable\": true,\n" +
+ " \"tracking_number\": \"9400111899560334651289\",\n" +
+ " \"tracking_status\": \"in_transit\",\n" +
+ " \"voided\": false,\n" +
+ " \"voided_at\": null\n" +
+ "}")
+ .withDelay(TimeUnit.SECONDS, 1));
+
+ String labelId = "se-1234";
+ Map labelParams = new HashMap<>() {{
+ put("label_layout", "4x6");
+ put("label_format", "pdf");
+ put("label_download_type", "url");
+ }};
+
+ Map labelData = new ShipEngine(customConfig).createLabelFromRateId(labelId, labelParams);
+ assertEquals("se-799373193", labelData.get("label_id"));
+ }
+
+ @Test
+ public void successfulVoidLabelWithLabelId() {
+ String labelId = "se-799373193";
+ new MockServerClient("127.0.0.1", 1080)
+ .when(request()
+ .withMethod("GET")
+ .withPath("/v1/labels/se-799373193/void"),
+ Times.exactly(1))
+ .respond(response()
+ .withStatusCode(200)
+ .withBody("{\n" +
+ " \"approved\": true,\n" +
+ " \"message\": \"This label has been voided.\"\n" +
+ "}")
+ .withDelay(TimeUnit.SECONDS, 1));
+
+ Map voidLabelResult = new ShipEngine(customConfig).voidLabelWithLabelId(labelId);
+ assertEquals("This label has been voided.", voidLabelResult.get("message"));
+ }
+
+ @Test
+ public void successfulGetRateFromShipmentDetails() {
+ new MockServerClient("127.0.0.1", 1080)
+ .when(request()
+ .withMethod("POST")
+ .withPath("/v1/rates"),
+ Times.exactly(1))
+ .respond(response()
+ .withStatusCode(200)
+ .withBody("{\n" +
+ " \"shipmentId\": \"se-141694059\",\n" +
+ " \"carrierId\": \"se-161650\",\n" +
+ " \"serviceCode\": \"usps_first_class_mail\",\n" +
+ " \"externalOrderId\": null,\n" +
+ " \"items\": [],\n" +
+ " \"taxIdentifiers\": null,\n" +
+ " \"externalShipmentId\": null,\n" +
+ " \"shipDate\": \"2021-07-28T00:00:00Z\",\n" +
+ " \"createdAt\": \"2021-07-28T16:56:40.257Z\",\n" +
+ " \"modifiedAt\": \"2021-07-28T16:56:40.223Z\",\n" +
+ " \"shipmentStatus\": \"pending\",\n" +
+ " \"shipTo\": {\n" +
+ " \"name\": \"James Atkinson\",\n" +
+ " \"phone\": null,\n" +
+ " \"companyName\": null,\n" +
+ " \"addressLine1\": \"28793 Fox Fire Lane\",\n" +
+ " \"addressLine2\": null,\n" +
+ " \"addressLine3\": null,\n" +
+ " \"cityLocality\": \"Shell Knob\",\n" +
+ " \"stateProvince\": \"MO\",\n" +
+ " \"postalCode\": \"65747\",\n" +
+ " \"countryCode\": \"US\",\n" +
+ " \"addressResidentialIndicator\": \"yes\"\n" +
+ " },\n" +
+ " \"shipFrom\": {\n" +
+ " \"name\": \"Medals of America\",\n" +
+ " \"phone\": \"800-308-0849\",\n" +
+ " \"companyName\": null,\n" +
+ " \"addressLine1\": \"114 Southchase Blvd\",\n" +
+ " \"addressLine2\": null,\n" +
+ " \"addressLine3\": null,\n" +
+ " \"cityLocality\": \"Fountain Inn\",\n" +
+ " \"stateProvince\": \"SC\",\n" +
+ " \"postalCode\": \"29644\",\n" +
+ " \"countryCode\": \"US\",\n" +
+ " \"addressResidentialIndicator\": \"unknown\"\n" +
+ " },\n" +
+ " \"warehouseId\": null,\n" +
+ " \"returnTo\": {\n" +
+ " \"name\": \"Medals of America\",\n" +
+ " \"phone\": \"800-308-0849\",\n" +
+ " \"companyName\": null,\n" +
+ " \"addressLine1\": \"114 Southchase Blvd\",\n" +
+ " \"addressLine2\": null,\n" +
+ " \"addressLine3\": null,\n" +
+ " \"cityLocality\": \"Fountain Inn\",\n" +
+ " \"stateProvince\": \"SC\",\n" +
+ " \"postalCode\": \"29644\",\n" +
+ " \"countryCode\": \"US\",\n" +
+ " \"addressResidentialIndicator\": \"unknown\"\n" +
+ " },\n" +
+ " \"confirmation\": \"none\",\n" +
+ " \"customs\": {\n" +
+ " \"contents\": \"merchandise\",\n" +
+ " \"nonDelivery\": \"return_to_sender\",\n" +
+ " \"customsItems\": []\n" +
+ " },\n" +
+ " \"advancedOptions\": {\n" +
+ " \"billToAccount\": null,\n" +
+ " \"billToCountryCode\": null,\n" +
+ " \"billToParty\": null,\n" +
+ " \"billToPostalCode\": null,\n" +
+ " \"containsAlcohol\": null,\n" +
+ " \"deliveryDutyPaid\": null,\n" +
+ " \"dryIce\": null,\n" +
+ " \"dryIceWeight\": null,\n" +
+ " \"nonMachinable\": null,\n" +
+ " \"saturdayDelivery\": null,\n" +
+ " \"useUPSGroundFreightPricing\": null,\n" +
+ " \"freightClass\": null,\n" +
+ " \"customField1\": null,\n" +
+ " \"customField2\": null,\n" +
+ " \"customField3\": null,\n" +
+ " \"originType\": null,\n" +
+ " \"shipperRelease\": null,\n" +
+ " \"collectOnDelivery\": null\n" +
+ " },\n" +
+ " \"originType\": null,\n" +
+ " \"insuranceProvider\": \"none\",\n" +
+ " \"tags\": [],\n" +
+ " \"orderSourceCode\": null,\n" +
+ " \"packages\": [\n" +
+ " {\n" +
+ " \"packageCode\": \"package\",\n" +
+ " \"weight\": {\n" +
+ " \"value\": 2.9,\n" +
+ " \"unit\": \"ounce\"\n" +
+ " },\n" +
+ " \"dimensions\": {\n" +
+ " \"unit\": \"inch\",\n" +
+ " \"length\": 0,\n" +
+ " \"width\": 0,\n" +
+ " \"height\": 0\n" +
+ " },\n" +
+ " \"insuredValue\": {\n" +
+ " \"currency\": \"usd\",\n" +
+ " \"amount\": 0\n" +
+ " },\n" +
+ " \"trackingNumber\": null,\n" +
+ " \"labelMessages\": {\n" +
+ " \"reference1\": \"4051492\",\n" +
+ " \"reference2\": null,\n" +
+ " \"reference3\": null\n" +
+ " },\n" +
+ " \"externalPackageId\": null\n" +
+ " }\n" +
+ " ],\n" +
+ " \"totalWeight\": {\n" +
+ " \"value\": 2.9,\n" +
+ " \"unit\": \"ounce\"\n" +
+ " },\n" +
+ " \"rateResponse\": {\n" +
+ " \"rates\": [\n" +
+ " {\n" +
+ " \"rateId\": \"se-784001113\",\n" +
+ " \"rateType\": \"shipment\",\n" +
+ " \"carrierId\": \"se-161650\",\n" +
+ " \"shippingAmount\": {\n" +
+ " \"currency\": \"usd\",\n" +
+ " \"amount\": 3.12\n" +
+ " },\n" +
+ " \"insuranceAmount\": {\n" +
+ " \"currency\": \"usd\",\n" +
+ " \"amount\": 0\n" +
+ " },\n" +
+ " \"confirmationAmount\": {\n" +
+ " \"currency\": \"usd\",\n" +
+ " \"amount\": 0\n" +
+ " },\n" +
+ " \"otherAmount\": {\n" +
+ " \"currency\": \"usd\",\n" +
+ " \"amount\": 0\n" +
+ " },\n" +
+ " \"taxAmount\": null,\n" +
+ " \"zone\": 5,\n" +
+ " \"packageType\": \"package\",\n" +
+ " \"deliveryDays\": 3,\n" +
+ " \"guaranteedService\": false,\n" +
+ " \"estimatedDeliveryDate\": \"2021-07-31T00:00:00Z\",\n" +
+ " \"carrierDeliveryDays\": \"3\",\n" +
+ " \"shipDate\": \"2021-07-28T00:00:00Z\",\n" +
+ " \"negotiatedRate\": false,\n" +
+ " \"serviceType\": \"USPS First Class Mail\",\n" +
+ " \"serviceCode\": \"usps_first_class_mail\",\n" +
+ " \"trackable\": true,\n" +
+ " \"carrierCode\": \"usps\",\n" +
+ " \"carrierNickname\": \"USPS\",\n" +
+ " \"carrierFriendlyName\": \"USPS\",\n" +
+ " \"validationStatus\": \"valid\",\n" +
+ " \"warningMessages\": [],\n" +
+ " \"errorMessages\": []\n" +
+ " }\n" +
+ " ],\n" +
+ " \"invalidRates\": [],\n" +
+ " \"rateRequestId\": \"se-85117731\",\n" +
+ " \"shipmentId\": \"se-141694059\",\n" +
+ " \"createdAt\": \"2021-07-28T16:56:40.6148892Z\",\n" +
+ " \"status\": \"completed\",\n" +
+ " \"errors\": []\n" +
+ " }\n" +
+ "}")
+ .withDelay(TimeUnit.SECONDS, 1));
+
+ Map shipmentDetails = new HashMap<>() {{
+ put("shipment", new HashMap<>() {{
+ put("carrier_id", "se-1234");
+ put("service_code", "usps_first_class_mail");
+ put("external_order_id", "string");
+ put("items", new ArrayList<>());
+ put("tax_identifiers", new ArrayList<>() {{
+ add(new HashMap<>() {{
+ put("taxable_entity_type", "shipper");
+ put("identifier_type", "vat");
+ put("issuing_authority", "string");
+ put("value", "string");
+ }});
+ }});
+ put("external_shipment_id", "string");
+ put("ship_date", "2018-09-23T00:00:00.000Z");
+ put("ship_to", new HashMap<>() {{
+ put("name", "John Doe");
+ put("phone", "1-123-456-7894");
+ put("company_name", "The Home Depot");
+ put("address_line1", "1999 Bishop Grandin Blvd.");
+ put("address_line2", "Unit 408");
+ put("address_line3", "Building #7");
+ put("city_locality", "Winnipeg");
+ put("state_province", "Manitoba");
+ put("postal_code", "78756");
+ put("country_code", "CA");
+ put("address_residential_indicator", "no");
+ }});
+ put("ship_from", new HashMap<>() {{
+ put("name", "John Doe");
+ put("phone", "1-123-456-7894");
+ put("company_name", "The Home Depot");
+ put("address_line1", "1999 Bishop Grandin Blvd.");
+ put("address_line2", "Unit 408");
+ put("address_line3", "Building #7");
+ put("city_locality", "Winnipeg");
+ put("state_province", "Manitoba");
+ put("postal_code", "78756");
+ put("country_code", "CA");
+ put("address_residential_indicator", "no");
+ }});
+ }});
+ }};
+
+ Map rateData = new ShipEngine(customConfig).getRatesWithShipmentDetails(shipmentDetails);
+ assertEquals("se-141694059", rateData.get("shipmentId"));
}
-}
+}
\ No newline at end of file