Skip to content

Commit

Permalink
Refactor update error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
malkusch committed Nov 6, 2023
1 parent 598dac8 commit 5630633
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 30 deletions.
26 changes: 12 additions & 14 deletions src/main/java/de/malkusch/km200/KM200.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()));
}
Expand Down Expand Up @@ -212,7 +210,8 @@ public String query(String path) throws KM200Exception, IOException, Interrupted

private HttpResponse<byte[]> 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();
Expand All @@ -231,18 +230,17 @@ private HttpResponse<byte[]> get(String path) throws IOException, InterruptedExc
}
}

private HttpResponse<byte[]> _get_unsafe(String path) throws IOException, InterruptedException {
var request = request(path).GET().build();

HttpResponse<byte[]> response;
response = http.send(request, BodyHandlers.ofByteArray());
private <T> HttpResponse<T> send(HttpRequest request, BodyHandler<T> 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());
};
}

Expand Down
90 changes: 74 additions & 16 deletions src/test/java/de/malkusch/km200/KM200Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -96,72 +96,130 @@ 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);

assertThrows(KM200Exception.NotFound.class, () -> km200.queryString("/non-existing"));
}

@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);

assertThrows(KM200Exception.Forbidden.class, () -> km200.queryString("/forbidden"));
}

@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);

assertThrows(KM200Exception.ServerError.class, () -> km200.queryString("/server-error"));
}

@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);

assertThrows(KM200Exception.Locked.class, () -> km200.update("/locked", 42));
}

@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);

assertThrows(HttpTimeoutException.class, () -> km200.query("/timeout"));
}

@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);

assertThrows(HttpTimeoutException.class, () -> km200.query("/timeout-body"));
}

@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")
Expand All @@ -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())
Expand All @@ -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")
Expand All @@ -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())
Expand Down

0 comments on commit 5630633

Please sign in to comment.