From e89b4e0d7d1298a1eb8e412dd0d3e16968df799e Mon Sep 17 00:00:00 2001 From: Jared King Date: Mon, 4 Dec 2017 14:06:12 -0600 Subject: [PATCH 1/3] handle full spectrum of error responses (#8) + handle rate limiting errors (#5) --- .../java/com/invoiced/entity/Connection.java | 81 ++++++------- .../com/invoiced/exception/ApiException.java | 2 +- .../com/invoiced/exception/AuthException.java | 2 +- .../com/invoiced/exception/ConnException.java | 3 +- .../invoiced/exception/EntityException.java | 2 +- .../exception/InvalidRequestException.java | 9 ++ .../invoiced/exception/InvoicedException.java | 13 ++ .../exception/RateLimitException.java | 9 ++ .../com/invoiced/entity/ConnectionTest.java | 112 +++++++++++++++--- .../resources/mappings/connection_rr_55.json | 19 +++ .../resources/mappings/connection_rr_56.json | 19 +++ .../resources/mappings/connection_rr_57.json | 19 +++ .../resources/mappings/connection_rr_8.json | 2 +- 13 files changed, 228 insertions(+), 64 deletions(-) create mode 100644 src/main/java/com/invoiced/exception/InvalidRequestException.java create mode 100644 src/main/java/com/invoiced/exception/InvoicedException.java create mode 100644 src/main/java/com/invoiced/exception/RateLimitException.java create mode 100644 src/test/resources/mappings/connection_rr_55.json create mode 100644 src/test/resources/mappings/connection_rr_56.json create mode 100644 src/test/resources/mappings/connection_rr_57.json diff --git a/src/main/java/com/invoiced/entity/Connection.java b/src/main/java/com/invoiced/entity/Connection.java index 56b1bb9..e4ad476 100644 --- a/src/main/java/com/invoiced/entity/Connection.java +++ b/src/main/java/com/invoiced/entity/Connection.java @@ -5,6 +5,9 @@ import com.invoiced.exception.ApiException; import com.invoiced.exception.AuthException; import com.invoiced.exception.ConnException; +import com.invoiced.exception.InvalidRequestException; +import com.invoiced.exception.InvoicedException; +import com.invoiced.exception.RateLimitException; import com.invoiced.util.ListResponse; import com.invoiced.util.Util; import com.mashape.unirest.http.HttpResponse; @@ -23,8 +26,7 @@ public class Connection { private boolean testMode; private boolean autoRefresh; - protected String post(String url, HashMap queryParms, String jsonBody) - throws ApiException, AuthException, ConnException { + protected String post(String url, HashMap queryParms, String jsonBody) throws InvoicedException { String responseString = ""; int responseCode = -1; @@ -35,8 +37,8 @@ protected String post(String url, HashMap queryParms, String jso try { HttpResponse response = Unirest.post(url).basicAuth(this.apiKey, "") - .header("accept", Connection.Accept).header("Content-Type", "application/json") - .queryString(queryParms).body(jsonBody).asString(); + .header("accept", Connection.Accept).header("Content-Type", "application/json") + .queryString(queryParms).body(jsonBody).asString(); responseString = response.getBody().toString(); responseCode = response.getStatus(); @@ -44,10 +46,8 @@ protected String post(String url, HashMap queryParms, String jso throw new ConnException(c); } - if (responseCode == 401) { - throw new AuthException(responseString); - } else if (responseCode == 400 || responseCode == 403 || responseCode == 404) { - throw new ApiException(responseString); + if (responseCode >= 400) { + throw this.handleApiError(responseCode, responseString); } return responseString; @@ -61,7 +61,7 @@ public static void closeAll() { } } - protected String patch(String url, String jsonBody) throws ApiException, AuthException, ConnException { + protected String patch(String url, String jsonBody) throws InvoicedException { String responseString = ""; int responseCode = -1; @@ -72,8 +72,8 @@ protected String patch(String url, String jsonBody) throws ApiException, AuthExc try { HttpResponse response = Unirest.patch(url).basicAuth(this.apiKey, "") - .header("accept", Connection.Accept).header("Content-Type", "application/json").body(jsonBody) - .asString(); + .header("accept", Connection.Accept).header("Content-Type", "application/json").body(jsonBody) + .asString(); responseString = response.getBody().toString(); responseCode = response.getStatus(); @@ -81,17 +81,14 @@ protected String patch(String url, String jsonBody) throws ApiException, AuthExc throw new ConnException(c); } - if (responseCode == 401) { - throw new AuthException(responseString); - } else if (responseCode == 400 || responseCode == 403 || responseCode == 404) { - throw new ApiException(responseString); + if (responseCode >= 400) { + throw this.handleApiError(responseCode, responseString); } return responseString; } - protected String get(String url, HashMap queryParms) - throws ApiException, AuthException, ConnException { + protected String get(String url, HashMap queryParms) throws InvoicedException { String responseString = ""; int responseCode = -1; @@ -102,8 +99,8 @@ protected String get(String url, HashMap queryParms) try { HttpResponse response = Unirest.get(url).basicAuth(this.apiKey, "") - .header("accept", Connection.Accept).header("Content-Type", "application/json") - .queryString(queryParms).asString(); + .header("accept", Connection.Accept).header("Content-Type", "application/json") + .queryString(queryParms).asString(); responseString = response.getBody().toString(); responseCode = response.getStatus(); @@ -112,19 +109,14 @@ protected String get(String url, HashMap queryParms) throw new ConnException(c); } - if (responseCode == 401) { - throw new AuthException(responseString); - } else if (responseCode == 400 || responseCode == 403 || responseCode == 404) { - throw new ApiException(responseString); - } else if (responseCode != 200 && responseCode != 204 && responseCode != 204) { - throw new ApiException(responseString); + if (responseCode >= 400) { + throw this.handleApiError(responseCode, responseString); } return responseString; } - protected ListResponse getList(String url, HashMap queryParms) - throws ApiException, AuthException, ConnException { + protected ListResponse getList(String url, HashMap queryParms) throws InvoicedException { String responseString = ""; int responseCode = -1; @@ -137,8 +129,8 @@ protected ListResponse getList(String url, HashMap queryParms) try { HttpResponse response = Unirest.get(url).basicAuth(this.apiKey, "") - .header("accept", Connection.Accept).header("Content-Type", "application/json") - .queryString(queryParms).asString(); + .header("accept", Connection.Accept).header("Content-Type", "application/json") + .queryString(queryParms).asString(); responseString = response.getBody().toString(); responseCode = response.getStatus(); @@ -155,16 +147,14 @@ protected ListResponse getList(String url, HashMap queryParms) throw new ConnException(c); } - if (responseCode == 401) { - throw new AuthException(responseString); - } else if (responseCode == 400 || responseCode == 403 || responseCode == 404) { - throw new ApiException(responseString); + if (responseCode >= 400) { + throw this.handleApiError(responseCode, responseString); } return apiResult; } - protected void delete(String url) throws ApiException, AuthException, ConnException { + protected void delete(String url) throws InvoicedException { int responseCode = -1; String responseString = ""; @@ -175,7 +165,7 @@ protected void delete(String url) throws ApiException, AuthException, ConnExcept try { HttpResponse response = Unirest.delete(url).basicAuth(this.apiKey, "") - .header("accept", Connection.Accept).header("Content-Type", "application/json").asString(); + .header("accept", Connection.Accept).header("Content-Type", "application/json").asString(); responseCode = response.getStatus(); @@ -187,14 +177,8 @@ protected void delete(String url) throws ApiException, AuthException, ConnExcept throw new ConnException(c); } - if (responseCode == 401) { - throw new AuthException(responseString); - } else if (responseCode == 400 || responseCode == 403 || responseCode == 404) { - throw new ApiException(responseString); - } - - if (responseCode != 204) { - throw new ApiException("Object not deleted"); + if (responseCode >= 400) { + throw this.handleApiError(responseCode, responseString); } } @@ -258,4 +242,15 @@ protected final String baseUrl() { return baseEndPointProduction; } + protected final InvoicedException handleApiError(int responseCode, String responseBody) { + if (responseCode == 401) { + return new AuthException(responseBody); + } else if (responseCode == 400) { + return new InvalidRequestException(responseBody); + } else if (responseCode == 429) { + return new RateLimitException(responseBody); + } + + return new ApiException(responseBody); + } } diff --git a/src/main/java/com/invoiced/exception/ApiException.java b/src/main/java/com/invoiced/exception/ApiException.java index 6dd8a00..fb0e712 100644 --- a/src/main/java/com/invoiced/exception/ApiException.java +++ b/src/main/java/com/invoiced/exception/ApiException.java @@ -1,6 +1,6 @@ package com.invoiced.exception; -public class ApiException extends Exception { +public class ApiException extends InvoicedException { private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/invoiced/exception/AuthException.java b/src/main/java/com/invoiced/exception/AuthException.java index ffdb28d..198e6a0 100644 --- a/src/main/java/com/invoiced/exception/AuthException.java +++ b/src/main/java/com/invoiced/exception/AuthException.java @@ -1,6 +1,6 @@ package com.invoiced.exception; -public class AuthException extends Exception { +public class AuthException extends InvoicedException { private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/invoiced/exception/ConnException.java b/src/main/java/com/invoiced/exception/ConnException.java index 2bdd33b..44d66bd 100644 --- a/src/main/java/com/invoiced/exception/ConnException.java +++ b/src/main/java/com/invoiced/exception/ConnException.java @@ -1,11 +1,10 @@ package com.invoiced.exception; -public class ConnException extends Exception { +public class ConnException extends InvoicedException { private static final long serialVersionUID = 1L; public ConnException(Throwable cause) { super(cause); } - } \ No newline at end of file diff --git a/src/main/java/com/invoiced/exception/EntityException.java b/src/main/java/com/invoiced/exception/EntityException.java index dd7652c..fd5c982 100644 --- a/src/main/java/com/invoiced/exception/EntityException.java +++ b/src/main/java/com/invoiced/exception/EntityException.java @@ -1,6 +1,6 @@ package com.invoiced.exception; -public class EntityException extends Exception { +public class EntityException extends InvoicedException { private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/invoiced/exception/InvalidRequestException.java b/src/main/java/com/invoiced/exception/InvalidRequestException.java new file mode 100644 index 0000000..ed96ed8 --- /dev/null +++ b/src/main/java/com/invoiced/exception/InvalidRequestException.java @@ -0,0 +1,9 @@ +package com.invoiced.exception; + +public class InvalidRequestException extends InvoicedException { + private static final long serialVersionUID = 1L; + + public InvalidRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/com/invoiced/exception/InvoicedException.java b/src/main/java/com/invoiced/exception/InvoicedException.java new file mode 100644 index 0000000..9dc72c1 --- /dev/null +++ b/src/main/java/com/invoiced/exception/InvoicedException.java @@ -0,0 +1,13 @@ +package com.invoiced.exception; + +abstract public class InvoicedException extends Exception { + private static final long serialVersionUID = 1L; + + public InvoicedException(Throwable cause) { + super(cause); + } + + public InvoicedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/invoiced/exception/RateLimitException.java b/src/main/java/com/invoiced/exception/RateLimitException.java new file mode 100644 index 0000000..90bee34 --- /dev/null +++ b/src/main/java/com/invoiced/exception/RateLimitException.java @@ -0,0 +1,9 @@ +package com.invoiced.exception; + +public class RateLimitException extends InvoicedException { + private static final long serialVersionUID = 1L; + + public RateLimitException(String message) { + super(message); + } +} diff --git a/src/test/java/com/invoiced/entity/ConnectionTest.java b/src/test/java/com/invoiced/entity/ConnectionTest.java index 9359f6a..0e0cc69 100644 --- a/src/test/java/com/invoiced/entity/ConnectionTest.java +++ b/src/test/java/com/invoiced/entity/ConnectionTest.java @@ -12,6 +12,8 @@ import com.invoiced.exception.ApiException; import com.invoiced.exception.AuthException; import com.invoiced.exception.ConnException; +import com.invoiced.exception.InvalidRequestException; +import com.invoiced.exception.RateLimitException; import com.invoiced.util.Util; public class ConnectionTest { @@ -68,10 +70,6 @@ public void testGetFail() { fail(e1.getMessage()); } return; - } catch (AuthException e) { - fail(e.getMessage()); - } catch (ConnException e) { - fail(e.getMessage()); } catch (Exception e) { fail(e.getMessage()); } @@ -113,10 +111,6 @@ public void testDeleteFail() { conn.delete(url); } catch (ApiException e) { return; - } catch (AuthException e) { - fail(e.getMessage()); - } catch (ConnException e) { - fail(e.getMessage()); } catch (Exception e) { fail(e.getMessage()); } @@ -151,7 +145,7 @@ public void testUpdate() { @Test public void testMockUpdateFail() { - // references connection_rr_5.json + // references connection_rr_6.json String expectedJson = "{\n \"type\": \"invalid_request\",\n \"message\": \"Customer was not found: 77777\"\n}"; @@ -209,31 +203,119 @@ public void testCreate() { } @Test - public void testCreateFail() { + public void testCreateFailInvalidRequest() { + + // references connection_rr_8.json String expectedJson = "{\n \"type\": \"invalid_request\",\n \"message\": \"Name missing\",\n \"param\": \"name\"\n}"; - String jsonBody = "{ \n \n \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\" \n }"; + String jsonBody = "{ \n \n \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"invalid_request\":true \n }"; Connection conn = new Connection("", true); conn.testModeOn(); try { - String url = conn.baseUrl() + "/" + "customers"; + String url = conn.baseUrl() + "/customers"; conn.post(url, null, jsonBody); - } catch (ApiException e) { + } catch (InvalidRequestException e) { try { assertTrue("Response is incorrect", Util.jsonEqual(expectedJson, e.getMessage())); } catch (IOException e1) { fail(e1.getMessage()); } - } catch (AuthException e) { + } catch (Exception e) { fail(e.getMessage()); - } catch (ConnException e) { + } + + } + + @Test + public void testCreateFailRateLimit() { + + // references connection_rr_55.json + + String expectedJson = "{\n \"type\": \"rate_limit_error\",\n \"message\": \"You have reached your rate limit\"}"; + + String jsonBody = "{ \n \n \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"rate_limit\":true \n }"; + + Connection conn = new Connection("", true); + + conn.testModeOn(); + + try { + String url = conn.baseUrl() + "/customers"; + + conn.post(url, null, jsonBody); + + } catch (RateLimitException e) { + try { + assertTrue("Response is incorrect", Util.jsonEqual(expectedJson, e.getMessage())); + } catch (IOException e1) { + fail(e1.getMessage()); + } + } catch (Exception e) { + fail(e.getMessage()); + } + + } + + @Test + public void testCreateFailAuthenticationError() { + + // references connection_rr_56.json + + String expectedJson = "{\n \"type\": \"authentication_error\",\n \"message\": \"Invalid API key: XXXX\",\n \"param\": \"name\"\n}"; + + String jsonBody = "{ \n \n \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"auth_error\":true \n }"; + + Connection conn = new Connection("", true); + + conn.testModeOn(); + + try { + String url = conn.baseUrl() + "/customers"; + + conn.post(url, null, jsonBody); + + } catch (AuthException e) { + try { + assertTrue("Response is incorrect", Util.jsonEqual(expectedJson, e.getMessage())); + } catch (IOException e1) { + fail(e1.getMessage()); + } + } catch (Exception e) { fail(e.getMessage()); + } + + } + + @Test + public void testCreateFailServerError() { + + // references connection_rr_57.json + + String expectedJson = "{\n \"type\": \"server_error\",\n \"message\": \"Internal Server Error\"\n}"; + + String jsonBody = "{ \n \n \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"server_error\":true \n }"; + + Connection conn = new Connection("", true); + + conn.testModeOn(); + + try { + String url = conn.baseUrl() + "/customers"; + + conn.post(url, null, jsonBody); + + } catch (ApiException e) { + try { + assertTrue("Response is incorrect", Util.jsonEqual(expectedJson, e.getMessage())); + } catch (IOException e1) { + fail(e1.getMessage()); + } } catch (Exception e) { fail(e.getMessage()); } diff --git a/src/test/resources/mappings/connection_rr_55.json b/src/test/resources/mappings/connection_rr_55.json new file mode 100644 index 0000000..a6d2350 --- /dev/null +++ b/src/test/resources/mappings/connection_rr_55.json @@ -0,0 +1,19 @@ + { + "request": { + "method": "POST", + "url": "/customers", + "bodyPatterns": [ + { + "equalToJson": "{ \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"rate_limit\":true \n }" + } + ] + + }, + "response": { + "status": 429, + "body": "{\n \"type\": \"rate_limit_error\",\n \"message\": \"You have reached your rate limit\"}", + "headers": { + "Content-Type": "application/json" + } + } +} \ No newline at end of file diff --git a/src/test/resources/mappings/connection_rr_56.json b/src/test/resources/mappings/connection_rr_56.json new file mode 100644 index 0000000..2e16dfd --- /dev/null +++ b/src/test/resources/mappings/connection_rr_56.json @@ -0,0 +1,19 @@ + { + "request": { + "method": "POST", + "url": "/customers", + "bodyPatterns": [ + { + "equalToJson": "{ \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"auth_error\":true \n }" + } + ] + + }, + "response": { + "status": 401, + "body": "{\n \"type\": \"authentication_error\",\n \"message\": \"Invalid API key: XXXX\",\n \"param\": \"name\"\n}", + "headers": { + "Content-Type": "application/json" + } + } +} \ No newline at end of file diff --git a/src/test/resources/mappings/connection_rr_57.json b/src/test/resources/mappings/connection_rr_57.json new file mode 100644 index 0000000..774c122 --- /dev/null +++ b/src/test/resources/mappings/connection_rr_57.json @@ -0,0 +1,19 @@ + { + "request": { + "method": "POST", + "url": "/customers", + "bodyPatterns": [ + { + "equalToJson": "{ \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"server_error\":true \n }" + } + ] + + }, + "response": { + "status": 500, + "body": "{\n \"type\": \"server_error\",\n \"message\": \"Internal Server Error\"\n}", + "headers": { + "Content-Type": "application/json" + } + } +} \ No newline at end of file diff --git a/src/test/resources/mappings/connection_rr_8.json b/src/test/resources/mappings/connection_rr_8.json index 5166b05..03bbe40 100644 --- a/src/test/resources/mappings/connection_rr_8.json +++ b/src/test/resources/mappings/connection_rr_8.json @@ -4,7 +4,7 @@ "url": "/customers", "bodyPatterns": [ { - "equalToJson": "{ \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\" \n }" + "equalToJson": "{ \"email\":\"billing@acmecorp.com\",\n \"collection_mode\":\"manual\",\n \"payment_terms\":\"NET 30\",\n \"type\":\"company\",\n \"invalid_request\":true \n }" } ] From 81e0a21066c79bf5b2a8a0ca201469cd86913b12 Mon Sep 17 00:00:00 2001 From: Jared King Date: Mon, 4 Dec 2017 15:11:45 -0600 Subject: [PATCH 2/3] use included gradle wrapper instead of system version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 498cc96..ce15084 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ jdk: - oraclejdk8 - oraclejdk7 script: -- gradle -i check +- ./gradlew -i check after_success: - ./gradlew cobertura coveralls notifications: From 57bb52d09ecef2dd5ffa80a299cdc941c8fdb5b7 Mon Sep 17 00:00:00 2001 From: Jared King Date: Mon, 4 Dec 2017 15:13:38 -0600 Subject: [PATCH 3/3] test on precise in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ce15084..e675475 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: java +dist: precise jdk: - oraclejdk8 - oraclejdk7