diff --git a/pom.xml b/pom.xml index 58f68fa..da5b051 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ jackson-datatype-jsr310 2.10.0 + + com.squareup.okhttp3 + okhttp + 4.9.1 + org.junit.jupiter diff --git a/samples/example-app/pom.xml b/samples/example-app/pom.xml index 20da70b..d092da6 100644 --- a/samples/example-app/pom.xml +++ b/samples/example-app/pom.xml @@ -6,13 +6,13 @@ com.exceptionless example-app - 1.0-beta1 + 1.0 com.exceptionless exceptionless-client - 1.0 + 1.0-beta1 diff --git a/samples/example-app/src/main/java/com/exceptionless/sample/app/Main.java b/samples/example-app/src/main/java/com/exceptionless/sample/app/Main.java index 31c7abc..fbad5bd 100644 --- a/samples/example-app/src/main/java/com/exceptionless/sample/app/Main.java +++ b/samples/example-app/src/main/java/com/exceptionless/sample/app/Main.java @@ -1,24 +1,40 @@ package com.exceptionless.sample.app; import com.exceptionless.exceptionlessclient.ExceptionlessClient; +import com.exceptionless.exceptionlessclient.models.EventPluginContext; public class Main { - public static void main(String[] args) { - ExceptionlessClient client = - ExceptionlessClient.from( - System.getenv("EXCEPTIONLESS_SAMPLE_APP_API_KEY"), - System.getenv("EXCEPTIONLESS_SAMPLE_APP_SERVER_URL")); - - client.getConfigurationManager().useSessions(); - - client.submitSessionStart(); + private static final ExceptionlessClient client = + ExceptionlessClient.from( + System.getenv("EXCEPTIONLESS_SAMPLE_APP_API_KEY"), + System.getenv("EXCEPTIONLESS_SAMPLE_APP_SERVER_URL")); + public static void sampleEventSubmissions() { client.submitException(new RuntimeException("Test exception")); - client.submitUnhandledException(new RuntimeException("Test exception"),"Test submission method"); + client.submitUnhandledException( + new RuntimeException("Test exception"), "Test submission method"); client.submitFeatureUsage("Test feature"); client.submitLog("Test log"); client.submitNotFound("Test resource"); + } - client.submitSessionEnd("Test user id"); + public static void sampleUseOfSessions() { + client.getConfigurationManager().useSessions(); + client.submitEvent( + EventPluginContext.from(client.createSessionStart().userIdentity("test-user").build())); + client.submitSessionEnd("test-user"); + } + + public static void sampleUseOfUpdatingEmailAndDescription() { + client.submitEvent( + EventPluginContext.from( + client.createLog("test-log").referenceId("test-reference-id").build())); + client.updateEmailAndDescription("test-reference-id", "test@email.com", "test-description"); + } + + public static void main(String[] args) { + sampleEventSubmissions(); + sampleUseOfUpdatingEmailAndDescription(); + sampleUseOfSessions(); } } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SettingsClientException.java b/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SettingsClientException.java new file mode 100644 index 0000000..0e5a806 --- /dev/null +++ b/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SettingsClientException.java @@ -0,0 +1,11 @@ +package com.exceptionless.exceptionlessclient.exceptions; + +public class SettingsClientException extends RuntimeException{ + public SettingsClientException(Throwable cause) { + super(cause); + } + + public SettingsClientException(String message) { + super(message); + } +} diff --git a/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SubmissionClientException.java b/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SubmissionClientException.java new file mode 100644 index 0000000..cc63a65 --- /dev/null +++ b/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SubmissionClientException.java @@ -0,0 +1,11 @@ +package com.exceptionless.exceptionlessclient.exceptions; + +public class SubmissionClientException extends RuntimeException { + public SubmissionClientException(Throwable cause) { + super(cause); + } + + public SubmissionClientException(String message) { + super(message); + } +} diff --git a/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SubmissionException.java b/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SubmissionException.java deleted file mode 100644 index 3d45170..0000000 --- a/src/main/java/com/exceptionless/exceptionlessclient/exceptions/SubmissionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.exceptionless.exceptionlessclient.exceptions; - -public class SubmissionException extends RuntimeException { - public SubmissionException(Throwable cause) { - super(cause); - } - - public SubmissionException(String message) { - super(message); - } -} diff --git a/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SettingsResponse.java b/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SettingsResponse.java index 269d64e..9bff4db 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SettingsResponse.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SettingsResponse.java @@ -9,12 +9,15 @@ @Value @NonFinal public class SettingsResponse { - @Builder.Default Boolean success = false; + int code; + String body; ServerSettings settings; - Exception exception; - String message; - public Boolean isSuccess() { - return success; + public boolean isSuccess() { + return code >= 200 && code <= 299; + } + + public boolean isNotModified() { + return code == 304; } } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SubmissionResponse.java b/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SubmissionResponse.java index 793d588..dd0e898 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SubmissionResponse.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/models/submission/SubmissionResponse.java @@ -8,34 +8,34 @@ @Value @NonFinal public class SubmissionResponse { - int statusCode; - String message; + int code; + String body; public boolean isSuccess() { - return statusCode >= 200 && statusCode <= 299; + return code >= 200 && code <= 299; } public boolean isBadRequest() { - return statusCode == 400; + return code == 400; } public boolean isServiceUnavailable() { - return statusCode == 503; + return code == 503; } public boolean isPaymentRequired() { - return statusCode == 402; + return code == 402; } public boolean unableToAuthenticate() { - return statusCode == 401 || statusCode == 403; + return code == 401 || code == 403; } public boolean isNotFound() { - return statusCode == 404; + return code == 404; } public boolean isRequestEntityTooLarge() { - return statusCode == 413; + return code == 413; } } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueue.java b/src/main/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueue.java index 23bbd0a..dc90d7a 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueue.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueue.java @@ -1,7 +1,7 @@ package com.exceptionless.exceptionlessclient.queue; import com.exceptionless.exceptionlessclient.configuration.Configuration; -import com.exceptionless.exceptionlessclient.exceptions.SubmissionException; +import com.exceptionless.exceptionlessclient.exceptions.SubmissionClientException; import com.exceptionless.exceptionlessclient.models.Event; import com.exceptionless.exceptionlessclient.models.storage.StorageItem; import com.exceptionless.exceptionlessclient.models.submission.SubmissionResponse; @@ -83,7 +83,7 @@ private boolean shouldSuspendProcessing() { } @VisibleForTesting - Boolean isProcessingCurrentlySuspended(){ + Boolean isProcessingCurrentlySuspended() { return shouldSuspendProcessing(); } @@ -113,7 +113,7 @@ private boolean shouldDiscard() { @Override public void process() { - synchronized (this){ + synchronized (this) { if (processingQueue) { LOG.trace("Currently processing queue; Returning..."); return; @@ -136,11 +136,11 @@ public void process() { SubmissionResponse response = submissionClient.postEvents(events); processSubmissionResponse(response, storedEvents); eventPosted(response, events); - } catch (SubmissionException e) { - LOG.error("Error processing queue", e); + } catch (SubmissionClientException e) { + LOG.error("Error submitting events from queue", e); suspendProcessing(); } finally { - synchronized (this){ + synchronized (this) { processingQueue = false; } } @@ -176,7 +176,10 @@ private void processSubmissionResponse( } if (response.isNotFound() || response.isBadRequest()) { - LOG.error(String.format("Error while trying to submit data: %s", response.getMessage())); + LOG.error( + String.format( + "Error while trying to submit data, Code:%s, Body:%s", + response.getCode(), response.getBody())); suspendProcessing(Duration.ofMinutes(4)); removeEvents(storedEvents); return; @@ -195,7 +198,9 @@ private void processSubmissionResponse( return; } - LOG.error(String.format("Error submitting events: %s", response.getMessage())); + LOG.error( + String.format( + "Error submitting events, Code: %s, Body: %s", response.getCode(), response.getBody())); suspendProcessing(); } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClient.java b/src/main/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClient.java index 1857054..2cac50c 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClient.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClient.java @@ -1,30 +1,35 @@ package com.exceptionless.exceptionlessclient.settings; import com.exceptionless.exceptionlessclient.configuration.Configuration; +import com.exceptionless.exceptionlessclient.exceptions.SettingsClientException; import com.exceptionless.exceptionlessclient.models.submission.SettingsResponse; import com.exceptionless.exceptionlessclient.utils.Utils; import com.exceptionless.exceptionlessclient.utils.VisibleForTesting; import com.fasterxml.jackson.core.type.TypeReference; import lombok.Builder; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.time.Duration; public class DefaultSettingsClient implements SettingsClientIF { private final Configuration configuration; - private final HttpClient httpClient; + private final OkHttpClient httpClient; @Builder public DefaultSettingsClient(Configuration configuration) { this.configuration = configuration; - this.httpClient = HttpClient.newHttpClient(); + this.httpClient = + new OkHttpClient() + .newBuilder() + .connectTimeout(Duration.ofMillis(configuration.getSettingsClientTimeoutInMillis())) + .build(); } @VisibleForTesting - DefaultSettingsClient(Configuration configuration, HttpClient httpClient) { + DefaultSettingsClient(Configuration configuration, OkHttpClient httpClient) { this.configuration = configuration; this.httpClient = httpClient; } @@ -32,33 +37,35 @@ public DefaultSettingsClient(Configuration configuration) { @Override public SettingsResponse getSettings(long version) { try { - URI uri = - new URI( - String.format( - "%s/api/v2/projects/config?v=%s&access_token=%s", - configuration.getServerUrl(), version, configuration.getApiKey())); - - HttpRequest request = - HttpRequest.newBuilder() - .uri(uri) - .GET() - .header("User-Agent", Configuration.USER_AGENT) - .timeout(Duration.ofMillis(configuration.getSettingsClientTimeoutInMillis())) + Request request = + new Request.Builder() + .url( + String.format( + "%s/api/v2/projects/config?v=%s&access_token=%s", + configuration.getServerUrl(), version, configuration.getApiKey())) + .get() .build(); - HttpResponse response = - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + Response response = httpClient.newCall(request).execute(); - if (response.statusCode() != 200) { - return SettingsResponse.builder().success(false).message(response.body()).build(); + ResponseBody body = response.body(); + String bodyStr = body == null ? null : body.string(); + if (bodyStr == null) { + return SettingsResponse.builder().code(response.code()).body("").build(); + } + if (response.code() / 100 != 2) { + return SettingsResponse.builder().code(response.code()).body(bodyStr).build(); } ServerSettings serverSettings = - Utils.JSON_MAPPER.readValue(response.body(), new TypeReference() {}); - - return SettingsResponse.builder().success(true).settings(serverSettings).build(); + Utils.JSON_MAPPER.readValue(bodyStr, new TypeReference() {}); + return SettingsResponse.builder() + .code(response.code()) + .body(bodyStr) + .settings(serverSettings) + .build(); } catch (Exception e) { - return SettingsResponse.builder().success(false).exception(e).message(e.getMessage()).build(); + throw new SettingsClientException(e); } } } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/settings/SettingsManager.java b/src/main/java/com/exceptionless/exceptionlessclient/settings/SettingsManager.java index 6103119..45c6de9 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/settings/SettingsManager.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/settings/SettingsManager.java @@ -1,5 +1,6 @@ package com.exceptionless.exceptionlessclient.settings; +import com.exceptionless.exceptionlessclient.exceptions.SettingsClientException; import com.exceptionless.exceptionlessclient.models.storage.StorageItem; import com.exceptionless.exceptionlessclient.models.submission.SettingsResponse; import com.exceptionless.exceptionlessclient.storage.StorageProviderIF; @@ -66,20 +67,39 @@ public void updateSettings() { try { long currentVersion = getVersion(); - LOG.info(String.format("Checking for updated settings from: v%s", currentVersion)); + LOG.info(String.format("Checking for updated settings from v%s", currentVersion)); SettingsResponse response = settingsClient.getSettings(currentVersion); - if (!response.isSuccess()) { - LOG.warn(String.format("Unable to update settings: %s", response.getMessage())); + if (shouldNotUpdate(response)) { return; } + ServerSettings prevValue = getSavedServerSettings(); storageProvider.getSettings().save(response.getSettings()); propertyChangeSupport.firePropertyChange("settings", prevValue, response.getSettings()); + } catch (SettingsClientException e) { + LOG.error(String.format("Error retrieving settings for v%s", getVersion()), e); } finally { synchronized (this) { updatingSettings = false; } } } + + private boolean shouldNotUpdate(SettingsResponse response) { + if (response.isNotModified()) { + LOG.info("No need to update, settings are not modified"); + return true; + } + if (!response.isSuccess()) { + LOG.warn(String.format("Unable to update settings: %s", response.getBody())); + return true; + } + if (response.getSettings() == null) { + LOG.warn("Not settings returned by server!"); + return true; + } + + return false; + } } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClient.java b/src/main/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClient.java index d824902..1f7aeba 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClient.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClient.java @@ -1,7 +1,7 @@ package com.exceptionless.exceptionlessclient.submission; import com.exceptionless.exceptionlessclient.configuration.Configuration; -import com.exceptionless.exceptionlessclient.exceptions.SubmissionException; +import com.exceptionless.exceptionlessclient.exceptions.SubmissionClientException; import com.exceptionless.exceptionlessclient.models.Event; import com.exceptionless.exceptionlessclient.models.UserDescription; import com.exceptionless.exceptionlessclient.models.submission.SubmissionResponse; @@ -9,19 +9,15 @@ import com.exceptionless.exceptionlessclient.utils.Utils; import com.exceptionless.exceptionlessclient.utils.VisibleForTesting; import lombok.Builder; +import okhttp3.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URI; import java.net.URLEncoder; -import java.net.http.HttpClient; -import java.net.http.HttpHeaders; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; -import java.util.OptionalLong; +import java.util.Optional; import java.util.stream.Collectors; public class DefaultSubmissionClient implements SubmissionClientIF { @@ -30,18 +26,22 @@ public class DefaultSubmissionClient implements SubmissionClientIF { private final Configuration configuration; private final SettingsManager settingsManager; - private final HttpClient httpClient; + private final OkHttpClient httpClient; @Builder public DefaultSubmissionClient(Configuration configuration, SettingsManager settingsManager) { this.configuration = configuration; this.settingsManager = settingsManager; - this.httpClient = HttpClient.newHttpClient(); + this.httpClient = + new OkHttpClient() + .newBuilder() + .connectTimeout(Duration.ofMillis(configuration.getSubmissionClientTimeoutInMillis())) + .build(); } @VisibleForTesting DefaultSubmissionClient( - Configuration configuration, SettingsManager settingsManager, HttpClient httpClient) { + Configuration configuration, SettingsManager settingsManager, OkHttpClient httpClient) { this.configuration = configuration; this.settingsManager = settingsManager; this.httpClient = httpClient; @@ -67,38 +67,34 @@ public SubmissionResponse postUserDescription(String referenceId, UserDescriptio private SubmissionResponse postSubmission(String url, Object data) { try { - URI uri = new URI(url); String requestJSON = Utils.JSON_MAPPER.writeValueAsString(data); - - HttpRequest request = - HttpRequest.newBuilder() - .uri(uri) - .POST(HttpRequest.BodyPublishers.ofString(requestJSON)) - .header("Content-Type", "application/json") - .header("User-Agent", Configuration.USER_AGENT) - .timeout(Duration.ofMillis(configuration.getSubmissionClientTimeoutInMillis())) + Request request = + new Request.Builder() + .url(url) + .post(RequestBody.create(requestJSON, MediaType.parse("application/json"))) .build(); - HttpResponse response = - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + Response response = httpClient.newCall(request).execute(); - if (response.statusCode() == 200) { + if (response.code() / 100 == 2) { updateSettingsFromHeaders(response.headers()); } + ResponseBody body = response.body(); return SubmissionResponse.builder() - .statusCode(response.statusCode()) - .message(response.body()) + .code(response.code()) + .body(body == null ? "" : body.string()) .build(); } catch (Exception e) { - throw new SubmissionException(e); + throw new SubmissionClientException(e); } } - private void updateSettingsFromHeaders(HttpHeaders headers) { - OptionalLong maybeSettingsVersion = headers.firstValueAsLong(CONFIGURATION_VERSION_HEADER); + private void updateSettingsFromHeaders(Headers headers) { + Optional maybeSettingsVersion = + Optional.ofNullable(headers.get(CONFIGURATION_VERSION_HEADER)); if (maybeSettingsVersion.isPresent()) { - settingsManager.checkVersion(maybeSettingsVersion.getAsLong()); + settingsManager.checkVersion(Long.parseLong(maybeSettingsVersion.get())); } else { LOG.error("No config version header was returned"); } @@ -107,33 +103,28 @@ private void updateSettingsFromHeaders(HttpHeaders headers) { @Override public void sendHeartBeat(String sessionIdOrUserId, boolean closeSession) { try { - URI uri = - new URI( - String.format( - "%s/api/v2/events/session/heartbeat?id=%s&close=%s&access_token=%s", - configuration.getHeartbeatServerUrl(), - URLEncoder.encode(sessionIdOrUserId, StandardCharsets.UTF_8), - closeSession, - configuration.getApiKey())); - HttpRequest request = - HttpRequest.newBuilder() - .uri(uri) - .GET() - .header("X-Exceptionless-Client", Configuration.USER_AGENT) - .timeout(Duration.ofMillis(configuration.getSubmissionClientTimeoutInMillis())) + Request request = + new Request.Builder() + .url( + String.format( + "%s/api/v2/events/session/heartbeat?id=%s&close=%s&access_token=%s", + configuration.getHeartbeatServerUrl(), + URLEncoder.encode(sessionIdOrUserId, StandardCharsets.UTF_8), + closeSession, + configuration.getApiKey())) + .get() .build(); - HttpResponse response = - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + Response response = httpClient.newCall(request).execute(); - if (response.statusCode() != 200) { + if (response.code() / 100 != 2) { LOG.error( String.format( "Error in submitting heartbeat to the server for sessionOrUserId: %s", sessionIdOrUserId)); } } catch (Exception e) { - throw new SubmissionException(e); + throw new SubmissionClientException(e); } } } diff --git a/src/main/java/com/exceptionless/exceptionlessclient/utils/Utils.java b/src/main/java/com/exceptionless/exceptionlessclient/utils/Utils.java index ce320a3..8cf688d 100644 --- a/src/main/java/com/exceptionless/exceptionlessclient/utils/Utils.java +++ b/src/main/java/com/exceptionless/exceptionlessclient/utils/Utils.java @@ -12,6 +12,7 @@ public final class Utils { public static final ObjectMapper JSON_MAPPER; + static { JSON_MAPPER = new ObjectMapper(); JSON_MAPPER.registerModule(new JavaTimeModule()); diff --git a/src/test/java/com/exceptionless/exceptionlessclient/ExceptionlessClientTest.java b/src/test/java/com/exceptionless/exceptionlessclient/ExceptionlessClientTest.java index 52815d8..3a024ec 100644 --- a/src/test/java/com/exceptionless/exceptionlessclient/ExceptionlessClientTest.java +++ b/src/test/java/com/exceptionless/exceptionlessclient/ExceptionlessClientTest.java @@ -55,7 +55,7 @@ public void setup() { public void itCanSetupASettingsChangeTimer() throws InterruptedException { doReturn(settingsStorage).when(storageProvider).getSettings(); settingsStorage.save(ServerSettings.builder().version(3L).build()); - doReturn(SettingsResponse.builder().message("test-message").success(false).build()) + doReturn(SettingsResponse.builder().body("test-message").code(400).build()) .when(settingsClient) .getSettings(anyLong()); @@ -281,7 +281,7 @@ public void itCanSubmitSessionEnd() { @Test public void itCanUpdateEmailAndDescription() { - SubmissionResponse expectedResponse = SubmissionResponse.builder().statusCode(200).build(); + SubmissionResponse expectedResponse = SubmissionResponse.builder().code(200).build(); doReturn(expectedResponse) .when(submissionClient) .postUserDescription( diff --git a/src/test/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueueTest.java b/src/test/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueueTest.java index 4d75999..1dbb717 100644 --- a/src/test/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueueTest.java +++ b/src/test/java/com/exceptionless/exceptionlessclient/queue/DefaultEventQueueTest.java @@ -2,7 +2,7 @@ import com.exceptionless.exceptionlessclient.TestFixtures; import com.exceptionless.exceptionlessclient.configuration.Configuration; -import com.exceptionless.exceptionlessclient.exceptions.SubmissionException; +import com.exceptionless.exceptionlessclient.exceptions.SubmissionClientException; import com.exceptionless.exceptionlessclient.models.Event; import com.exceptionless.exceptionlessclient.models.submission.SubmissionResponse; import com.exceptionless.exceptionlessclient.storage.InMemoryStorage; @@ -63,7 +63,7 @@ public void itCanProcessABatchSuccessfully() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(200).build(); + SubmissionResponse.builder().body("test-message").code(200).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -80,7 +80,7 @@ public void itShouldNotProcessIfCurrentlyProcessingEvents() storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(200).build(); + SubmissionResponse.builder().body("test-message").code(200).build(); doAnswer( invocationOnMock -> { Thread.sleep(1000); @@ -111,7 +111,7 @@ public void itShouldNotPostEmptyEvents() { public void itShouldSuspendProcessingOnClientException() { storage.save(event); - doThrow(new SubmissionException("test")).when(submissionClient).postEvents(List.of(event)); + doThrow(new SubmissionClientException("test")).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); queue.process(); @@ -126,7 +126,7 @@ public void itShouldSuspendProcessingIfServiceIsUnavailable() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(503).build(); + SubmissionResponse.builder().body("test-message").code(503).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -142,7 +142,7 @@ public void itShouldSuspendAndDiscardProcessingAndClearQueueIfNoPayment() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(402).build(); + SubmissionResponse.builder().body("test-message").code(402).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -162,7 +162,7 @@ public void itShouldSuspendProcessingAndClearQueueIfUnableToAuthenticate() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(401).build(); + SubmissionResponse.builder().body("test-message").code(401).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -178,7 +178,7 @@ public void itShouldSuspendProcessingAndClearQueueForNotFoundResponse() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(404).build(); + SubmissionResponse.builder().body("test-message").code(404).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -194,7 +194,7 @@ public void itShouldSuspendProcessingAndClearQueueForBadRequest() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(400).build(); + SubmissionResponse.builder().body("test-message").code(400).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -210,7 +210,7 @@ public void itShouldSuspendProcessingByDefault() { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(-1).build(); + SubmissionResponse.builder().body("test-message").code(-1).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); queue.onEventsPosted(testHandler); @@ -238,7 +238,7 @@ public void itShouldReduceSubmissionBatchSizeIfRequestEntitiesAreTooLarge() { } SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(413).build(); + SubmissionResponse.builder().body("test-message").code(413).build(); doReturn(response).when(submissionClient).postEvents(anyList()); queue.onEventsPosted(testHandler); @@ -264,7 +264,7 @@ public void itShouldDiscardEventsIfItCantReduceSubmissionSizeAndRequestEntitiesA } SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(413).build(); + SubmissionResponse.builder().body("test-message").code(413).build(); doReturn(response).when(submissionClient).postEvents(anyList()); queue.onEventsPosted(testHandler); @@ -290,12 +290,12 @@ public void itShouldResetSubmissionBatchSizeOnNextSuccessfulResponse() { storage.save(event); } - doReturn(SubmissionResponse.builder().message("test-message").statusCode(413).build()) + doReturn(SubmissionResponse.builder().body("test-message").code(413).build()) .when(submissionClient) .postEvents(anyList()); queue.process(); - doReturn(SubmissionResponse.builder().message("test-message").statusCode(200).build()) + doReturn(SubmissionResponse.builder().body("test-message").code(200).build()) .when(submissionClient) .postEvents(anyList()); queue.process(); @@ -314,7 +314,7 @@ public void itShouldResetSubmissionBatchSizeOnNextSuccessfulResponse() { public void itShouldProcessEventsUsingTimer() throws InterruptedException { storage.save(event); SubmissionResponse response = - SubmissionResponse.builder().message("test-message").statusCode(200).build(); + SubmissionResponse.builder().body("test-message").code(200).build(); doReturn(response).when(submissionClient).postEvents(List.of(event)); Configuration configuration = diff --git a/src/test/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClientTest.java b/src/test/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClientTest.java index d3e60ca..3eadc2a 100644 --- a/src/test/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClientTest.java +++ b/src/test/java/com/exceptionless/exceptionlessclient/settings/DefaultSettingsClientTest.java @@ -2,7 +2,9 @@ import com.exceptionless.exceptionlessclient.TestFixtures; import com.exceptionless.exceptionlessclient.configuration.Configuration; +import com.exceptionless.exceptionlessclient.exceptions.SettingsClientException; import com.exceptionless.exceptionlessclient.models.submission.SettingsResponse; +import okhttp3.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -10,12 +12,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; -import java.time.Duration; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doReturn; @@ -23,9 +23,10 @@ @ExtendWith(MockitoExtension.class) public class DefaultSettingsClientTest { - @Mock private HttpClient httpClient; - @Mock private HttpResponse response; + @Mock private OkHttpClient httpClient; + @Mock private Call call; private DefaultSettingsClient settingsClient; + private Response.Builder responseBuilder; @BeforeEach public void setup() { @@ -36,74 +37,77 @@ public void setup() { .settingsClientTimeoutInMillis(10) .build(); settingsClient = new DefaultSettingsClient(configuration, httpClient); + responseBuilder = + new Response.Builder() + .request(new Request.Builder().url("http://test-url").build()) + .protocol(Protocol.HTTP_2) + .message("test-message") + .body(ResponseBody.create("test-body", MediaType.get("text/plain"))) + .code(200); } @Test - public void itCanHandleASuccessfulResponse() throws IOException, InterruptedException { - doReturn(response) + public void itCanHandleASuccessfulResponse() throws IOException { + doReturn( + responseBuilder + .body( + ResponseBody.create( + "{\n" + + "\t\"version\":1,\n" + + "\t\"settings\":{\n" + + "\t\t\"key\":\"value\"\n" + + "\t}\n" + + "}", + MediaType.get("application/json"))) + .build()) + .when(call) + .execute(); + doReturn(call) .when(httpClient) - .send( + .newCall( argThat( httpRequest -> httpRequest.method().equals("GET") - && httpRequest.timeout().isPresent() - && httpRequest.timeout().get().equals(Duration.ofMillis(10)) - && httpRequest.headers().firstValue("User-Agent").isPresent() - && httpRequest - .headers() - .firstValue("User-Agent") - .get() - .equals("exceptionless-java") && httpRequest - .uri() + .url() .toString() .equals( - "http://test-server-url/api/v2/projects/config?v=1&access_token=test-api-key")), - any()); - doReturn( + "http://test-server-url/api/v2/projects/config?v=1&access_token=test-api-key"))); + + SettingsResponse response = settingsClient.getSettings(1); + + assertThat(response.getBody()) + .isEqualTo( "{\n" + "\t\"version\":1,\n" + "\t\"settings\":{\n" + "\t\t\"key\":\"value\"\n" + "\t}\n" - + "}") - .when(response) - .body(); - doReturn(200).when(response).statusCode(); - - SettingsResponse response = settingsClient.getSettings(1); - - assertThat(response.getMessage()).isNull(); - assertThat(response.getException()).isNull(); - assertThat(response.isSuccess()).isTrue(); + + "}"); + assertThat(response.getCode()).isEqualTo(200); assertThat(response.getSettings()) .isEqualTo(ServerSettings.builder().version(1L).settings(Map.of("key", "value")).build()); } @Test - public void itCanHandleAUnsuccessfulResponse() throws IOException, InterruptedException { - doReturn(response).when(httpClient).send(any(), any()); - doReturn("test-response").when(response).body(); - doReturn(400).when(response).statusCode(); + public void itCanHandleNullSettingsReturnedByTheServer() throws IOException { + doReturn(responseBuilder.body(null).build()).when(call).execute(); + doReturn(call).when(httpClient).newCall(any()); SettingsResponse response = settingsClient.getSettings(1); - assertThat(response.getMessage()).isEqualTo("test-response"); - assertThat(response.getException()).isNull(); - assertThat(response.isSuccess()).isFalse(); + assertThat(response.getBody()).isEqualTo(""); + assertThat(response.getCode()).isEqualTo(200); assertThat(response.getSettings()).isNull(); } @Test - public void itCanHandleAnyException() throws IOException, InterruptedException { + public void itCanMapAnyExceptionToSettingsClientException() { RuntimeException e = new RuntimeException("test"); - doThrow(e).when(httpClient).send(any(), any()); + doThrow(e).when(httpClient).newCall(any()); - SettingsResponse response = settingsClient.getSettings(1); - - assertThat(response.getMessage()).isEqualTo("test"); - assertThat(response.getException()).isEqualTo(e); - assertThat(response.isSuccess()).isFalse(); - assertThat(response.getSettings()).isNull(); + assertThatThrownBy(() -> settingsClient.getSettings(1)) + .isInstanceOf(SettingsClientException.class) + .hasMessage("java.lang.RuntimeException: test"); } } diff --git a/src/test/java/com/exceptionless/exceptionlessclient/settings/SettingsManagerTest.java b/src/test/java/com/exceptionless/exceptionlessclient/settings/SettingsManagerTest.java index 9a36697..6d6c5cd 100644 --- a/src/test/java/com/exceptionless/exceptionlessclient/settings/SettingsManagerTest.java +++ b/src/test/java/com/exceptionless/exceptionlessclient/settings/SettingsManagerTest.java @@ -1,5 +1,6 @@ package com.exceptionless.exceptionlessclient.settings; +import com.exceptionless.exceptionlessclient.exceptions.SettingsClientException; import com.exceptionless.exceptionlessclient.models.submission.SettingsResponse; import com.exceptionless.exceptionlessclient.storage.InMemoryStorage; import com.exceptionless.exceptionlessclient.storage.InMemoryStorageProvider; @@ -87,7 +88,7 @@ public void itCanUpdateSettingsIfCheckedVersionIsGreaterThanCurrentVersion() { ServerSettings newSettingsFromServer = ServerSettings.builder().version(4L).settings(Map.of("new-key", "new-value")).build(); - doReturn(SettingsResponse.builder().success(true).settings(newSettingsFromServer).build()) + doReturn(SettingsResponse.builder().code(200).settings(newSettingsFromServer).build()) .when(settingsClient) .getSettings(3); @@ -104,10 +105,7 @@ public void itWillLetOnlyOneThreadUpdateSettingsAtATime() doAnswer( invocation -> { Thread.sleep(1000); - return SettingsResponse.builder() - .success(true) - .settings(newSettingsFromServer) - .build(); + return SettingsResponse.builder().code(200).settings(newSettingsFromServer).build(); }) .when(settingsClient) .getSettings(anyLong()); @@ -121,8 +119,8 @@ public void itWillLetOnlyOneThreadUpdateSettingsAtATime() } @Test - public void itWillNotSaveSettingsIfNotAbleToGetSettingsFromServer() { - doReturn(SettingsResponse.builder().success(false).message("test-message").build()) + public void itWillNotSaveSettingsIfSettingsAreNotModified() { + doReturn(SettingsResponse.builder().code(304).body("test-message").build()) .when(settingsClient) .getSettings(anyLong()); @@ -131,11 +129,40 @@ public void itWillNotSaveSettingsIfNotAbleToGetSettingsFromServer() { assertThat(storage.peek()).isNull(); } + @Test + public void itWillNotSaveSettingsIfUnableToGetSettingsFromServer() { + doReturn(SettingsResponse.builder().code(400).body("test-message").build()) + .when(settingsClient) + .getSettings(anyLong()); + + settingsManager.updateSettings(); + + assertThat(storage.peek()).isNull(); + } + + @Test + public void itWillNotSaveSettingsIfNoSettingsReturnedByTheServer() { + doReturn(SettingsResponse.builder().code(200).body("test-message").build()) + .when(settingsClient) + .getSettings(anyLong()); + + settingsManager.updateSettings(); + + assertThat(storage.peek()).isNull(); + } + + @Test + public void itCanHandleSettingsClientException() { + doThrow(new SettingsClientException("test")).when(settingsClient).getSettings(anyLong()); + + settingsManager.updateSettings(); + } + @Test public void itCanSuccessfullyUpdateSettingsAndNotifyListeners() { ServerSettings newSettingsFromServer = ServerSettings.builder().version(4L).settings(Map.of("new-key", "new-value")).build(); - doReturn(SettingsResponse.builder().success(true).settings(newSettingsFromServer).build()) + doReturn(SettingsResponse.builder().code(200).settings(newSettingsFromServer).build()) .when(settingsClient) .getSettings(anyLong()); diff --git a/src/test/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClientTest.java b/src/test/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClientTest.java index 0f23506..b3cabb2 100644 --- a/src/test/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClientTest.java +++ b/src/test/java/com/exceptionless/exceptionlessclient/submission/DefaultSubmissionClientTest.java @@ -2,11 +2,12 @@ import com.exceptionless.exceptionlessclient.TestFixtures; import com.exceptionless.exceptionlessclient.configuration.Configuration; -import com.exceptionless.exceptionlessclient.exceptions.SubmissionException; +import com.exceptionless.exceptionlessclient.exceptions.SubmissionClientException; import com.exceptionless.exceptionlessclient.models.Event; import com.exceptionless.exceptionlessclient.models.UserDescription; import com.exceptionless.exceptionlessclient.models.submission.SubmissionResponse; import com.exceptionless.exceptionlessclient.settings.SettingsManager; +import okhttp3.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -14,10 +15,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; -import java.net.http.HttpClient; -import java.net.http.HttpHeaders; -import java.net.http.HttpResponse; -import java.time.Duration; import java.util.List; import java.util.Map; @@ -30,9 +27,10 @@ @ExtendWith(MockitoExtension.class) public class DefaultSubmissionClientTest { @Mock private SettingsManager settingsManager; - @Mock private HttpClient httpClient; - @Mock private HttpResponse httpResponse; + @Mock private OkHttpClient httpClient; + @Mock private Call call; private DefaultSubmissionClient submissionClient; + private Response.Builder responseBuilder; @BeforeEach public void setup() { @@ -45,184 +43,141 @@ public void setup() { .build(); submissionClient = new DefaultSubmissionClient(configuration, settingsManager, httpClient); + responseBuilder = + new Response.Builder() + .request(new Request.Builder().url("http://test-url").build()) + .protocol(Protocol.HTTP_2) + .body(ResponseBody.create("test-body", MediaType.get("text/plain"))) + .message("test-message") + .code(200); } @Test - public void itCanPostEventsSuccessfully() throws IOException, InterruptedException { - doReturn("test-body").when(httpResponse).body(); - doReturn(200).when(httpResponse).statusCode(); - doReturn(HttpHeaders.of(Map.of("x-exceptionless-configversion", List.of("3")), (s, s2) -> true)) - .when(httpResponse) - .headers(); - doReturn(httpResponse) + public void itCanPostEventsSuccessfully() throws IOException { + Response response = + responseBuilder.headers(Headers.of(Map.of("x-exceptionless-configversion", "3"))).build(); + doReturn(response).when(call).execute(); + doReturn(call) .when(httpClient) - .send( + .newCall( argThat( request -> request - .uri() + .url() .toString() .equals( "http://test-server-url/api/v2/events?access_token=test-api-key") - && request.method().equals("POST") - && request.headers().firstValue("Content-Type").isPresent() - && request - .headers() - .firstValue("Content-Type") - .get() - .equals("application/json") - && request.headers().firstValue("User-Agent").isPresent() - && request - .headers() - .firstValue("User-Agent") - .get() - .equals("exceptionless-java") - && request.timeout().isPresent() - && request.timeout().get().equals(Duration.ofMillis(10))), - any()); + && request.method().equals("POST"))); SubmissionResponse submissionResponse = submissionClient.postEvents(List.of(Event.builder().build())); - assertThat(submissionResponse.getMessage()).isEqualTo("test-body"); - assertThat(submissionResponse.getStatusCode()).isEqualTo(200); + assertThat(submissionResponse.getBody()).isEqualTo("test-body"); + assertThat(submissionResponse.getCode()).isEqualTo(200); verify(settingsManager, times(1)).checkVersion(3); } @Test - public void itCanPostEventsSuccessfullyWhenNoSettingHeaderIsReturned() - throws IOException, InterruptedException { - doReturn("test-body").when(httpResponse).body(); - doReturn(200).when(httpResponse).statusCode(); - doReturn(HttpHeaders.of(Map.of(), (s, s2) -> false)).when(httpResponse).headers(); - doReturn(httpResponse).when(httpClient).send(any(), any()); + public void itCanPostEventsSuccessfullyWhenNoSettingHeaderIsReturned() throws IOException { + doReturn(responseBuilder.build()).when(call).execute(); + doReturn(call).when(httpClient).newCall(any()); SubmissionResponse submissionResponse = submissionClient.postEvents(List.of(Event.builder().build())); - assertThat(submissionResponse.getMessage()).isEqualTo("test-body"); - assertThat(submissionResponse.getStatusCode()).isEqualTo(200); + assertThat(submissionResponse.getBody()).isEqualTo("test-body"); + assertThat(submissionResponse.getCode()).isEqualTo(200); verifyZeroInteractions(settingsManager); } @Test - public void itCanThrowAllExceptionsAsSubmissionExceptionWhilePostingEvents() - throws IOException, InterruptedException { - doThrow(new RuntimeException("test")).when(httpClient).send(any(), any()); + public void itCanThrowAllExceptionsAsSubmissionExceptionWhilePostingEvents() { + doThrow(new RuntimeException("test")).when(httpClient).newCall(any()); assertThatThrownBy(() -> submissionClient.postEvents(List.of(Event.builder().build()))) - .isInstanceOf(SubmissionException.class) + .isInstanceOf(SubmissionClientException.class) .hasMessage("java.lang.RuntimeException: test"); } @Test - public void itCanPostUserDescriptionSuccessfully() throws IOException, InterruptedException { - doReturn("test-body").when(httpResponse).body(); - doReturn(200).when(httpResponse).statusCode(); - doReturn(HttpHeaders.of(Map.of("x-exceptionless-configversion", List.of("3")), (s, s2) -> true)) - .when(httpResponse) - .headers(); - doReturn(httpResponse) + public void itCanPostUserDescriptionSuccessfully() throws IOException { + Response response = + responseBuilder.headers(Headers.of(Map.of("x-exceptionless-configversion", "3"))).build(); + doReturn(response).when(call).execute(); + doReturn(call) .when(httpClient) - .send( + .newCall( argThat( request -> request - .uri() + .url() .toString() .equals( "http://test-server-url/api/v2/events/by-ref/test-reference-id/user-description?access_token=test-api-key") - && request.method().equals("POST") - && request.headers().firstValue("Content-Type").isPresent() - && request - .headers() - .firstValue("Content-Type") - .get() - .equals("application/json") - && request.headers().firstValue("User-Agent").isPresent() - && request - .headers() - .firstValue("User-Agent") - .get() - .equals("exceptionless-java") - && request.timeout().isPresent() - && request.timeout().get().equals(Duration.ofMillis(10))), - any()); + && request.method().equals("POST"))); SubmissionResponse submissionResponse = submissionClient.postUserDescription( "test-reference-id", UserDescription.builder().build()); - assertThat(submissionResponse.getMessage()).isEqualTo("test-body"); - assertThat(submissionResponse.getStatusCode()).isEqualTo(200); + assertThat(submissionResponse.getBody()).isEqualTo("test-body"); + assertThat(submissionResponse.getCode()).isEqualTo(200); verify(settingsManager, times(1)).checkVersion(3); } @Test public void itCanPostUserDescriptionSuccessfullyWhenNoSettingHeaderIsReturned() - throws IOException, InterruptedException { - doReturn("test-body").when(httpResponse).body(); - doReturn(200).when(httpResponse).statusCode(); - doReturn(HttpHeaders.of(Map.of(), (s, s2) -> false)).when(httpResponse).headers(); - doReturn(httpResponse).when(httpClient).send(any(), any()); + throws IOException { + doReturn(responseBuilder.build()).when(call).execute(); + doReturn(call).when(httpClient).newCall(any()); SubmissionResponse submissionResponse = submissionClient.postUserDescription( "test-reference-id", UserDescription.builder().build()); - assertThat(submissionResponse.getMessage()).isEqualTo("test-body"); - assertThat(submissionResponse.getStatusCode()).isEqualTo(200); + assertThat(submissionResponse.getBody()).isEqualTo("test-body"); + assertThat(submissionResponse.getCode()).isEqualTo(200); verifyZeroInteractions(settingsManager); } @Test - public void itCanThrowAllExceptionsAsSubmissionExceptionWhilePostingUserDescription() - throws IOException, InterruptedException { - doThrow(new RuntimeException("test")).when(httpClient).send(any(), any()); + public void itCanThrowAllExceptionsAsSubmissionExceptionWhilePostingUserDescription() { + doThrow(new RuntimeException("test")).when(httpClient).newCall(any()); assertThatThrownBy( () -> submissionClient.postUserDescription( "test-reference-id", UserDescription.builder().build())) - .isInstanceOf(SubmissionException.class) + .isInstanceOf(SubmissionClientException.class) .hasMessage("java.lang.RuntimeException: test"); } @Test - public void itCanSendHeartbeatSuccessfully() throws IOException, InterruptedException { - doReturn(200).when(httpResponse).statusCode(); - doReturn(httpResponse).when(httpClient).send(any(), any()); - - submissionClient.sendHeartBeat("test-user-id", true); - - verify(httpClient, times(1)) - .send( + public void itCanSendHeartbeatSuccessfully() throws IOException { + doReturn(responseBuilder.build()).when(call).execute(); + doReturn(call) + .when(httpClient) + .newCall( argThat( request -> request - .uri() + .url() .toString() .equals( "http://test-heartbeat-server-url/api/v2/events/session/heartbeat?id=test-user-id&close=true&access_token=test-api-key") - && request.method().equals("GET") - && request.timeout().isPresent() - && request.timeout().get().equals(Duration.ofMillis(10)) - && request.headers().firstValue("X-Exceptionless-Client").isPresent() - && request - .headers() - .firstValue("X-Exceptionless-Client") - .get() - .equals("exceptionless-java")), - any()); + && request.method().equals("GET"))); + + submissionClient.sendHeartBeat("test-user-id", true); + + verify(call, times(1)).execute(); } @Test - public void itCanThrowAllExceptionsAsSubmissionExceptionWhileSendingHeartbeat() - throws IOException, InterruptedException { - doThrow(new RuntimeException("test")).when(httpClient).send(any(), any()); + public void itCanThrowAllExceptionsAsSubmissionExceptionWhileSendingHeartbeat() { + doThrow(new RuntimeException("test")).when(httpClient).newCall(any()); assertThatThrownBy(() -> submissionClient.sendHeartBeat("test-user-id", true)) - .isInstanceOf(SubmissionException.class) + .isInstanceOf(SubmissionClientException.class) .hasMessage("java.lang.RuntimeException: test"); } }