From 563063395ef97c931d809083fdba7d4fec0c2d5c Mon Sep 17 00:00:00 2001 From: Markus Malkusch Date: Mon, 6 Nov 2023 12:22:42 +0100 Subject: [PATCH] Refactor update error handling --- src/main/java/de/malkusch/km200/KM200.java | 26 +++--- .../java/de/malkusch/km200/KM200Test.java | 90 +++++++++++++++---- 2 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/main/java/de/malkusch/km200/KM200.java b/src/main/java/de/malkusch/km200/KM200.java index 314cd76..b5ca207 100644 --- a/src/main/java/de/malkusch/km200/KM200.java +++ b/src/main/java/de/malkusch/km200/KM200.java @@ -12,6 +12,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; import java.net.http.HttpResponse.BodyHandlers; import java.time.Duration; import java.time.LocalDateTime; @@ -172,12 +173,9 @@ private void update(String path, Object update) throws KM200Exception, IOExcepti throw new KM200Exception("Could not encrypt update " + json); } var request = request(path).POST(BodyPublishers.ofByteArray(encrypted)).build(); - var response = http.send(request, BodyHandlers.ofString()); + var response = send(request, BodyHandlers.ofString()); if (!(response.statusCode() >= 200 && response.statusCode() < 300)) { - if (response.statusCode() == 423) { - throw new KM200Exception.Locked(String.format("Failed to update %s with %s", path, json)); - } throw new KM200Exception( String.format("Failed to update %s [%d]: %s", path, response.statusCode(), response.body())); } @@ -212,7 +210,8 @@ public String query(String path) throws KM200Exception, IOException, Interrupted private HttpResponse get(String path) throws IOException, InterruptedException { try { - return retry.get(() -> _get_unsafe(path)); + var request = request(path).GET().build(); + return retry.get(() -> send(request, BodyHandlers.ofByteArray())); } catch (FailsafeException e) { var cause = e.getCause(); @@ -231,18 +230,17 @@ private HttpResponse get(String path) throws IOException, InterruptedExc } } - private HttpResponse _get_unsafe(String path) throws IOException, InterruptedException { - var request = request(path).GET().build(); - - HttpResponse response; - response = http.send(request, BodyHandlers.ofByteArray()); + private HttpResponse send(HttpRequest request, BodyHandler bodyHandler) + throws IOException, InterruptedException, KM200Exception { + var response = http.send(request, bodyHandler); return switch (response.statusCode()) { case 200 -> response; - case 403 -> throw new KM200Exception.Forbidden("Query " + path + " is forbidden"); - case 404 -> throw new KM200Exception.NotFound("Query " + path + " was not found"); - case 500 -> throw new KM200Exception.ServerError("Query " + path + " resulted in a server error"); - default -> throw new KM200Exception("Query " + path + " failed with response code " + response.statusCode()); + case 403 -> throw new KM200Exception.Forbidden(request.uri() + " is forbidden"); + case 404 -> throw new KM200Exception.NotFound(request.uri() + " was not found"); + case 423 -> throw new KM200Exception.Locked(request.uri() + " was locked"); + case 500 -> throw new KM200Exception.ServerError(request.uri() + " resulted in a server error"); + default -> throw new KM200Exception(request.uri() + " failed with response code " + response.statusCode()); }; } diff --git a/src/test/java/de/malkusch/km200/KM200Test.java b/src/test/java/de/malkusch/km200/KM200Test.java index c532d12..922523c 100644 --- a/src/test/java/de/malkusch/km200/KM200Test.java +++ b/src/test/java/de/malkusch/km200/KM200Test.java @@ -55,7 +55,7 @@ public void stubSystem() throws Exception { @ParameterizedTest @ValueSource(strings = { "http://localhost:" + PORT, "http://localhost:" + PORT + "/" }) - public void shouldReplaceSlashesInURI(String uri) throws Exception { + public void queryShouldReplaceSlashesInURI(String uri) throws Exception { stubFor(get("/gateway/DateTime").willReturn(ok(loadBody("gateway.DateTime")))); var km200 = new KM200(uri, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -65,7 +65,7 @@ public void shouldReplaceSlashesInURI(String uri) throws Exception { } @Test - public void shouldSendUserAgent() throws Exception { + public void queryShouldSendUserAgent() throws Exception { stubFor(get("/gateway/DateTime").willReturn(ok(loadBody("gateway.DateTime")))); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -75,7 +75,7 @@ public void shouldSendUserAgent() throws Exception { } @Test - public void shouldDecryptQuery() throws Exception { + public void queryShouldDecryptQuery() throws Exception { stubFor(get("/gateway/DateTime").willReturn(ok(loadBody("gateway.DateTime")))); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -85,7 +85,7 @@ public void shouldDecryptQuery() throws Exception { } @Test - public void shouldEncryptUpdate() throws Exception { + public void updateShouldEncrypt() throws Exception { stubFor(post("/gateway/DateTime").willReturn(ok())); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -96,7 +96,7 @@ public void shouldEncryptUpdate() throws Exception { } @Test - public void shouldFailOnNonExistingPath() throws Exception { + public void queryShouldFailOnNonExistingPath() throws Exception { stubFor(get("/non-existing").willReturn(notFound())); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -104,7 +104,15 @@ public void shouldFailOnNonExistingPath() throws Exception { } @Test - public void shouldFailOnForbiddenPath() throws Exception { + public void updateShouldFailOnNonExistingPath() throws Exception { + stubFor(post("/update-non-existing").willReturn(notFound())); + var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(KM200Exception.NotFound.class, () -> km200.update("/update-non-existing", 42)); + } + + @Test + public void queryShouldFailOnForbiddenPath() throws Exception { stubFor(get("/forbidden").willReturn(status(403))); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -112,7 +120,15 @@ public void shouldFailOnForbiddenPath() throws Exception { } @Test - public void shouldFailOnServerError() throws Exception { + public void updateShouldFailOnForbiddenPath() throws Exception { + stubFor(post("/update-forbidden").willReturn(status(403))); + var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(KM200Exception.Forbidden.class, () -> km200.update("/update-forbidden", 42)); + } + + @Test + public void queryShouldFailOnServerError() throws Exception { stubFor(get("/server-error").willReturn(serverError())); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -120,24 +136,49 @@ public void shouldFailOnServerError() throws Exception { } @Test - public void shouldFailOnUnknownError() throws Exception { + public void updateShouldFailOnServerError() throws Exception { + stubFor(post("/update-server-error").willReturn(serverError())); + var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(KM200Exception.ServerError.class, () -> km200.update("/update-server-error", 42)); + } + + @Test + public void queryShouldFailOnUnknownError() throws Exception { stubFor(get("/unknown-error").willReturn(status(599))); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); assertThrows(KM200Exception.class, () -> km200.queryString("/unknown-error")); } + @Test + public void updateShouldFailOnUnknownError() throws Exception { + stubFor(post("/update-unknown-error").willReturn(status(599))); + var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(KM200Exception.class, () -> km200.update("/update-unknown-error", 42)); + } + @ParameterizedTest @EnumSource(Fault.class) - public void shouldFailOnBadResponse(Fault fault) throws Exception { + public void queryShouldFailOnBadResponse(Fault fault) throws Exception { stubFor(get("/bad-response").willReturn(aResponse().withFault(fault))); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); assertThrows(IOException.class, () -> km200.queryString("/bad-response")); } + @ParameterizedTest + @EnumSource(Fault.class) + public void updateShouldFailOnBadResponse(Fault fault) throws Exception { + stubFor(post("/update-bad-response").willReturn(aResponse().withFault(fault))); + var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(IOException.class, () -> km200.update("/update-bad-response", 42)); + } + @Test - public void shouldFailOnLocked() throws Exception { + public void updateShouldFailOnLocked() throws Exception { stubFor(post("/locked").willReturn(status(423))); var km200 = new KM200(URI, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -145,7 +186,7 @@ public void shouldFailOnLocked() throws Exception { } @Test - public void shouldTimeout() throws Exception { + public void queryShouldTimeout() throws Exception { stubFor(get("/timeout").willReturn(ok(loadBody("gateway.DateTime")).withFixedDelay(100))); var km200 = new KM200(URI, Duration.ofMillis(50), GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -153,7 +194,15 @@ public void shouldTimeout() throws Exception { } @Test - public void shouldTimeoutResponseBody() throws Exception { + public void updateShouldTimeout() throws Exception { + stubFor(post("/update-timeout").willReturn(ok(loadBody("gateway.DateTime")).withFixedDelay(100))); + var km200 = new KM200(URI, Duration.ofMillis(50), GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(HttpTimeoutException.class, () -> km200.update("/update-timeout", 42)); + } + + @Test + public void queryShouldTimeoutResponseBody() throws Exception { stubFor(get("/timeout-body").willReturn(ok(loadBody("gateway.DateTime")).withChunkedDribbleDelay(5, 20000))); var km200 = new KM200(URI, Duration.ofMillis(50), GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); @@ -161,7 +210,16 @@ public void shouldTimeoutResponseBody() throws Exception { } @Test - public void shouldRetryOnTimeout() throws Exception { + public void updateShouldTimeoutResponseBody() throws Exception { + stubFor(post("/update-timeout-body") + .willReturn(ok(loadBody("gateway.DateTime")).withChunkedDribbleDelay(5, 20000))); + var km200 = new KM200(URI, Duration.ofMillis(50), GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(HttpTimeoutException.class, () -> km200.update("/update-timeout-body", 42)); + } + + @Test + public void queryShouldRetryOnTimeout() throws Exception { stubFor(get("/retry").inScenario("retry").whenScenarioStateIs(STARTED) .willReturn(notFound().withFixedDelay(100)).willSetStateTo("retry")); stubFor(get("/retry").inScenario("retry").whenScenarioStateIs("retry") @@ -175,7 +233,7 @@ public void shouldRetryOnTimeout() throws Exception { } @Test - public void shouldRetryOnServerError() throws Exception { + public void queryShouldRetryOnServerError() throws Exception { stubFor(get("/retry500").inScenario("retry500").whenScenarioStateIs(STARTED).willReturn(serverError()) .willSetStateTo("retry2")); stubFor(get("/retry500").inScenario("retry500").whenScenarioStateIs("retry2").willReturn(serverError()) @@ -194,7 +252,7 @@ public void shouldRetryOnServerError() throws Exception { @ParameterizedTest @EnumSource(Fault.class) - public void shouldRetryOnBadResponse(Fault fault) throws Exception { + public void queryShouldRetryOnBadResponse(Fault fault) throws Exception { stubFor(get("/retry-bad-response").inScenario("retryBadResponse").whenScenarioStateIs(STARTED) .willReturn(aResponse().withFault(fault)).willSetStateTo("retry2")); stubFor(get("/retry-bad-response").inScenario("retryBadResponse").whenScenarioStateIs("retry2") @@ -212,7 +270,7 @@ public void shouldRetryOnBadResponse(Fault fault) throws Exception { } @Test - public void shouldWaitWhenRetrying() throws Exception { + public void queryShouldWaitWhenRetrying() throws Exception { stubFor(get("/retry-wait").inScenario("retry-wait").whenScenarioStateIs(STARTED).willReturn(serverError()) .willSetStateTo("retry-wait2")); stubFor(get("/retry-wait").inScenario("retry-wait").whenScenarioStateIs("retry-wait2").willReturn(serverError())