From 1f6913c7d0b869bb052e73ffc8276c4770bea3c9 Mon Sep 17 00:00:00 2001 From: Diego Molina Date: Wed, 16 Sep 2026 15:46:09 +0200 Subject: [PATCH 1/2] Add v1/v2 Insights test, test case, error, and trend endpoints Extends InsightsEndpoint (previously limited to getTestResults) with getTestMetricsSummary, getTestTrends, getTests, getTestCases, getTestCasesStats, getErrors, getTrendsTests, and getTrendsErrors, covering the most commonly used parts of the Insights API. Adds matching parameter builders and response models under model.insights. Filters, Activity, Concurrency, Coverage, and the cross-source (all sources) endpoint variants are intentionally left for follow-up work; InsightsEndpoint remains annotated @Unfinished. Fixes #63 Co-Authored-By: Claude Sonnet 5 --- .../saucerest/api/InsightsEndpoint.java | 154 ++++++++++++- .../saucerest/model/insights/Aggs.java | 44 ++++ .../insights/AnalyticsTrendsParameter.java | 153 +++++++++++++ .../model/insights/DateTimeUtils.java | 21 ++ .../saucerest/model/insights/DeviceGroup.java | 13 ++ .../saucerest/model/insights/ErrorBucket.java | 10 + .../saucerest/model/insights/Errors.java | 9 + .../model/insights/ErrorsParameter.java | 149 ++++++++++++ .../saucerest/model/insights/Interval.java | 18 ++ .../saucerest/model/insights/NameCount.java | 6 + .../model/insights/ParameterUtils.java | 18 ++ .../saucerest/model/insights/Scope.java | 13 ++ .../saucerest/model/insights/Sort.java | 12 + .../saucerest/model/insights/Status.java | 17 ++ .../model/insights/TagFilterMode.java | 12 + .../saucerest/model/insights/TestCase.java | 16 ++ .../saucerest/model/insights/TestCases.java | 11 + .../model/insights/TestCasesParameter.java | 212 ++++++++++++++++++ .../model/insights/TestCasesSortBy.java | 23 ++ .../model/insights/TestCasesStats.java | 10 + .../insights/TestCasesStatsParameter.java | 176 +++++++++++++++ .../model/insights/TestExecution.java | 31 +++ .../saucerest/model/insights/TestMetrics.java | 6 + .../model/insights/TestMetricsAggs.java | 22 ++ .../model/insights/TestMetricsParameter.java | 124 ++++++++++ .../saucerest/model/insights/TestTrends.java | 10 + .../saucerest/model/insights/Tests.java | 11 + .../model/insights/TestsParameter.java | 212 ++++++++++++++++++ .../saucerest/model/insights/TestsSortBy.java | 12 + .../saucerest/model/insights/TimeRange.java | 17 ++ .../saucerest/model/insights/TrendBucket.java | 8 + .../model/insights/TrendsErrors.java | 9 + .../model/insights/TrendsErrorsParameter.java | 176 +++++++++++++++ .../saucerest/model/insights/TrendsMeta.java | 5 + .../model/insights/TrendsTestsParameter.java | 186 +++++++++++++++ .../saucerest/model/insights/Unit.java | 14 ++ .../integration/InsightsEndpointTest.java | 143 ++++++++++++ .../unit/AnalyticsTrendsParameterTest.java | 31 +++ .../saucerest/unit/ErrorsParameterTest.java | 37 +++ .../unit/TestCasesParameterTest.java | 45 ++++ .../unit/TestCasesStatsParameterTest.java | 34 +++ .../unit/TestMetricsParameterTest.java | 61 +++++ .../saucerest/unit/TestsParameterTest.java | 65 ++++++ .../unit/TrendsErrorsParameterTest.java | 33 +++ .../unit/TrendsTestsParameterTest.java | 33 +++ 45 files changed, 2416 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Aggs.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/DateTimeUtils.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/DeviceGroup.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/ErrorBucket.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Errors.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/ErrorsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Interval.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/NameCount.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/ParameterUtils.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Scope.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Sort.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Status.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TagFilterMode.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestCase.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestCases.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestCasesParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestCasesSortBy.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStats.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStatsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestExecution.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestMetrics.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsAggs.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestTrends.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Tests.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TestsSortBy.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TimeRange.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TrendBucket.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrors.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrorsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TrendsMeta.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/TrendsTestsParameter.java create mode 100644 src/main/java/com/saucelabs/saucerest/model/insights/Unit.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/AnalyticsTrendsParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/ErrorsParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/TestCasesParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/TestCasesStatsParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/TestMetricsParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/TestsParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/TrendsErrorsParameterTest.java create mode 100644 src/test/java/com/saucelabs/saucerest/unit/TrendsTestsParameterTest.java diff --git a/src/main/java/com/saucelabs/saucerest/api/InsightsEndpoint.java b/src/main/java/com/saucelabs/saucerest/api/InsightsEndpoint.java index c93722fe..2b52a4ab 100644 --- a/src/main/java/com/saucelabs/saucerest/api/InsightsEndpoint.java +++ b/src/main/java/com/saucelabs/saucerest/api/InsightsEndpoint.java @@ -2,13 +2,29 @@ import com.saucelabs.saucerest.DataCenter; import com.saucelabs.saucerest.HttpMethod; +import com.saucelabs.saucerest.JobSource; import com.saucelabs.saucerest.Unfinished; +import com.saucelabs.saucerest.model.insights.AnalyticsTrendsParameter; +import com.saucelabs.saucerest.model.insights.Errors; +import com.saucelabs.saucerest.model.insights.ErrorsParameter; +import com.saucelabs.saucerest.model.insights.TestCases; +import com.saucelabs.saucerest.model.insights.TestCasesParameter; +import com.saucelabs.saucerest.model.insights.TestCasesStats; +import com.saucelabs.saucerest.model.insights.TestCasesStatsParameter; +import com.saucelabs.saucerest.model.insights.TestMetrics; +import com.saucelabs.saucerest.model.insights.TestMetricsParameter; import com.saucelabs.saucerest.model.insights.TestResult; import com.saucelabs.saucerest.model.insights.TestResultParameter; +import com.saucelabs.saucerest.model.insights.Tests; +import com.saucelabs.saucerest.model.insights.TestsParameter; +import com.saucelabs.saucerest.model.insights.TestTrends; +import com.saucelabs.saucerest.model.insights.TrendsErrors; +import com.saucelabs.saucerest.model.insights.TrendsErrorsParameter; +import com.saucelabs.saucerest.model.insights.TrendsTestsParameter; import java.io.IOException; -import java.util.Map; -@Unfinished("This endpoint is not yet completely implemented") +@Unfinished("This endpoint does not yet cover the Filters, Activity, Concurrency, and Coverage Insights APIs, " + + "nor the cross-source (all sources) variants of the Test Cases, Errors, and Trends APIs") public class InsightsEndpoint extends AbstractEndpoint { public InsightsEndpoint(DataCenter dataCenter) { super(dataCenter); @@ -26,11 +42,137 @@ public InsightsEndpoint(String username, String accessKey, String apiServer) { super(username, accessKey, apiServer); } + /** + * Retrieves run data for all tests matching the specified criteria. + * + * @param parameter A {@link TestResultParameter} object containing the parameters to filter the results + * @return A {@link TestResult} object + * @throws IOException when the request fails + */ public TestResult getTestResults(TestResultParameter parameter) throws IOException { String url = getBaseEndpoint() + "v1/analytics/tests"; - Map params; - params = parameter.toMap(); - return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, params), TestResult.class); + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TestResult.class); } -} \ No newline at end of file + + /** + * Returns aggregated metric values, such as fastest/slowest run and status breakdown, for a specific test + * across a given period. + * + * @param parameter A {@link TestMetricsParameter} object containing the parameters to filter the results + * @return A {@link TestMetrics} object + * @throws IOException when the request fails + */ + public TestMetrics getTestMetricsSummary(TestMetricsParameter parameter) throws IOException { + String url = getBaseEndpoint() + "v1/analytics/insights/test-metrics"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TestMetrics.class); + } + + /** + * Returns time-bucketed data representing the tests executed in the requested interval. + * + * @param parameter A {@link AnalyticsTrendsParameter} object containing the parameters to filter the results + * @return A {@link TestTrends} object + * @throws IOException when the request fails + */ + public TestTrends getTestTrends(AnalyticsTrendsParameter parameter) throws IOException { + String url = getBaseEndpoint() + "v1/analytics/trends/tests"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TestTrends.class); + } + + /** + * Returns an array containing details of individual test executions matching the specified criteria. + * + * @param jobSource The type of device for which you are getting tests. Valid values are: {@link JobSource} + * @param parameter A {@link TestsParameter} object containing the parameters to filter the results + * @return A {@link Tests} object + * @throws IOException when the request fails + */ + public Tests getTests(JobSource jobSource, TestsParameter parameter) throws IOException { + String url = getBaseEndpoint(jobSource) + "tests"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), Tests.class); + } + + /** + * Returns an array of test cases, grouped by name, with statistical details such as run counts and pass/fail + * rates. + * + * @param jobSource The type of device for which you are getting test cases. Valid values are: {@link JobSource} + * @param parameter A {@link TestCasesParameter} object containing the parameters to filter the results + * @return A {@link TestCases} object + * @throws IOException when the request fails + */ + public TestCases getTestCases(JobSource jobSource, TestCasesParameter parameter) throws IOException { + String url = getBaseEndpoint(jobSource) + "test-cases"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TestCases.class); + } + + /** + * Returns a high-level statistical summary of test case consistency and reliability, such as counts of + * consistently passing, failing, or erroring test cases. + * + * @param jobSource The type of device for which you are getting test case stats. Valid values are: {@link + * JobSource} + * @param parameter A {@link TestCasesStatsParameter} object containing the parameters to filter the results + * @return A {@link TestCasesStats} object + * @throws IOException when the request fails + */ + public TestCasesStats getTestCasesStats(JobSource jobSource, TestCasesStatsParameter parameter) throws IOException { + String url = getBaseEndpoint(jobSource) + "test-cases/stats"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TestCasesStats.class); + } + + /** + * Returns an array of errors, with occurrence counts, for all tests matching the specified criteria. + * + * @param jobSource The type of device for which you are getting errors. Valid values are: {@link JobSource} + * @param parameter A {@link ErrorsParameter} object containing the parameters to filter the results + * @return A {@link Errors} object + * @throws IOException when the request fails + */ + public Errors getErrors(JobSource jobSource, ErrorsParameter parameter) throws IOException { + String url = getBaseEndpoint(jobSource) + "errors"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), Errors.class); + } + + /** + * Returns an array of buckets with aggregations, such as the number of tests run per browser, device, OS, or + * framework, for the requested interval. + * + * @param jobSource The type of device for which you are getting trends. Valid values are: {@link JobSource} + * @param parameter A {@link TrendsTestsParameter} object containing the parameters to filter the results + * @return A {@link TestTrends} object + * @throws IOException when the request fails + */ + public TestTrends getTrendsTests(JobSource jobSource, TrendsTestsParameter parameter) throws IOException { + String url = getBaseEndpoint(jobSource) + "trends/tests"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TestTrends.class); + } + + /** + * Returns error statistics, including the tests affected by each error, for the requested interval. + * + * @param jobSource The type of device for which you are getting error trends. Valid values are: {@link + * JobSource} + * @param parameter A {@link TrendsErrorsParameter} object containing the parameters to filter the results + * @return A {@link TrendsErrors} object + * @throws IOException when the request fails + */ + public TrendsErrors getTrendsErrors(JobSource jobSource, TrendsErrorsParameter parameter) throws IOException { + String url = getBaseEndpoint(jobSource) + "trends/errors"; + + return deserializeJSONObject(requestWithQueryParameters(url, HttpMethod.GET, parameter.toMap()), TrendsErrors.class); + } + + /** The base endpoint of the v2 Insights endpoint APIs. */ + protected String getBaseEndpoint(JobSource jobSource) { + return super.getBaseEndpoint() + "v2/insights/" + jobSource.value + "/"; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Aggs.java b/src/main/java/com/saucelabs/saucerest/model/insights/Aggs.java new file mode 100644 index 00000000..95ac7fb3 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Aggs.java @@ -0,0 +1,44 @@ +package com.saucelabs.saucerest.model.insights; + +import com.google.gson.annotations.SerializedName; +import java.util.List; + +public class Aggs { + public List browser; + + @SerializedName("browserError") + public List browserError; + + @SerializedName("browserFail") + public List browserFail; + + public List device; + + @SerializedName("deviceError") + public List deviceError; + + @SerializedName("deviceFail") + public List deviceFail; + + @SerializedName("errorMessage") + public List errorMessage; + + public List framework; + + @SerializedName("frameworkError") + public List frameworkError; + + @SerializedName("frameworkFail") + public List frameworkFail; + + public List os; + + @SerializedName("osError") + public List osError; + + @SerializedName("osFail") + public List osFail; + + public List owner; + public List status; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java new file mode 100644 index 00000000..d72076e9 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java @@ -0,0 +1,153 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v1/analytics/trends/tests}. */ +public class AnalyticsTrendsParameter { + private final String start; + private final String end; + private final TimeRange timeRange; + private final Scope scope; + private final Interval interval; + private final String[] browser; + private final String[] build; + private final String[] device; + private final String[] os; + private final TestResultParameter.Status status; + private final String[] tag; + private final TagFilterMode tagFilterMode; + + private AnalyticsTrendsParameter(Builder builder) { + start = builder.start; + end = builder.end; + timeRange = builder.timeRange; + scope = builder.scope; + interval = builder.interval; + browser = builder.browser; + build = builder.build; + device = builder.device; + os = builder.os; + status = builder.status; + tag = builder.tag; + tagFilterMode = builder.tagFilterMode; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("time_range", timeRange == null ? null : timeRange.toString()), + new AbstractMap.SimpleEntry<>("scope", scope == null ? null : scope.value), + new AbstractMap.SimpleEntry<>("interval", interval == null ? null : interval.value), + new AbstractMap.SimpleEntry<>("browser", browser), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("status", status == null ? null : status.getValue()), + new AbstractMap.SimpleEntry<>("tag", tag), + new AbstractMap.SimpleEntry<>("tag_filter_mode", tagFilterMode == null ? null : tagFilterMode.value) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private String start; + private String end; + private TimeRange timeRange; + private Scope scope; + private Interval interval; + private String[] browser; + private String[] build; + private String[] device; + private String[] os; + private TestResultParameter.Status status; + private String[] tag; + private TagFilterMode tagFilterMode; + + public Builder() { + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setTimeRange(TimeRange val) { + timeRange = val; + return this; + } + + public Builder setScope(Scope val) { + scope = val; + return this; + } + + public Builder setInterval(Interval val) { + interval = val; + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setStatus(TestResultParameter.Status val) { + status = val; + return this; + } + + public Builder setTag(String[] val) { + tag = val; + return this; + } + + public Builder setTagFilterMode(TagFilterMode val) { + tagFilterMode = val; + return this; + } + + public AnalyticsTrendsParameter build() { + boolean isTimeRangeUsed = timeRange != null; + boolean isStartEndUsed = start != null && end != null; + + if (!isTimeRangeUsed && !isStartEndUsed) { + throw new IllegalStateException("Either 'time_range' or 'start' and 'end' must be set."); + } + if (isTimeRangeUsed && isStartEndUsed) { + throw new IllegalStateException("Only one of 'time_range' or 'start' and 'end' can be set, not both."); + } + + return new AnalyticsTrendsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/DateTimeUtils.java b/src/main/java/com/saucelabs/saucerest/model/insights/DateTimeUtils.java new file mode 100644 index 00000000..6ac6e163 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/DateTimeUtils.java @@ -0,0 +1,21 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +final class DateTimeUtils { + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + private DateTimeUtils() { + } + + /** Converts a {@link LocalDateTime} in the system default time zone to a UTC API date string. */ + static String toUtcString(LocalDateTime val) { + ZoneOffset offset = ZoneId.systemDefault().getRules().getOffset(val); + LocalDateTime utcDateTime = val.minusSeconds(offset.getTotalSeconds()); + + return utcDateTime.format(FORMATTER); + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/DeviceGroup.java b/src/main/java/com/saucelabs/saucerest/model/insights/DeviceGroup.java new file mode 100644 index 00000000..8df26afd --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/DeviceGroup.java @@ -0,0 +1,13 @@ +package com.saucelabs.saucerest.model.insights; + +/** Only applicable when the {@link com.saucelabs.saucerest.JobSource} is RDC. */ +public enum DeviceGroup { + PRIVATE("private"), + PUBLIC("public"); + + public final String value; + + DeviceGroup(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/ErrorBucket.java b/src/main/java/com/saucelabs/saucerest/model/insights/ErrorBucket.java new file mode 100644 index 00000000..5e94300c --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/ErrorBucket.java @@ -0,0 +1,10 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; + +public class ErrorBucket { + public String name; + public Integer count; + public List items; + public Boolean hasMore; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Errors.java b/src/main/java/com/saucelabs/saucerest/model/insights/Errors.java new file mode 100644 index 00000000..6c8a018d --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Errors.java @@ -0,0 +1,9 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; + +public class Errors { + public List buckets; + public Integer allItemsCount; + public Integer total; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/ErrorsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/ErrorsParameter.java new file mode 100644 index 00000000..ddcd66c7 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/ErrorsParameter.java @@ -0,0 +1,149 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v2/insights/{source}/errors}. */ +public class ErrorsParameter { + private final String orgId; + private final Interval interval; + private final String userId; + private final String groupId; + private final String teamId; + private final String[] build; + private final String[] os; + private final String[] device; + private final String start; + private final String end; + private final Integer limit; + private final Integer offset; + private final String[] automationBackend; + + private ErrorsParameter(Builder builder) { + orgId = builder.orgId; + interval = builder.interval; + userId = builder.userId; + groupId = builder.groupId; + teamId = builder.teamId; + build = builder.build; + os = builder.os; + device = builder.device; + start = builder.start; + end = builder.end; + limit = builder.limit; + offset = builder.offset; + automationBackend = builder.automationBackend; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("org_id", orgId), + new AbstractMap.SimpleEntry<>("interval", interval == null ? null : interval.value), + new AbstractMap.SimpleEntry<>("user_id", userId), + new AbstractMap.SimpleEntry<>("group_id", groupId), + new AbstractMap.SimpleEntry<>("team_id", teamId), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("limit", limit), + new AbstractMap.SimpleEntry<>("offset", offset), + new AbstractMap.SimpleEntry<>("automation_backend", automationBackend) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String orgId; + private Interval interval; + private String userId; + private String groupId; + private String teamId; + private String[] build; + private String[] os; + private String[] device; + private String start; + private String end; + private Integer limit; + private Integer offset; + private String[] automationBackend; + + /** @param orgId The organization to return results for (required). */ + public Builder(String orgId) { + this.orgId = orgId; + } + + public Builder setInterval(Interval val) { + interval = val; + return this; + } + + public Builder setUserId(String val) { + userId = val; + return this; + } + + public Builder setGroupId(String val) { + groupId = val; + return this; + } + + public Builder setTeamId(String val) { + teamId = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setLimit(Integer val) { + limit = val; + return this; + } + + public Builder setOffset(Integer val) { + offset = val; + return this; + } + + public Builder setAutomationBackend(String[] val) { + automationBackend = val; + return this; + } + + public ErrorsParameter build() { + return new ErrorsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Interval.java b/src/main/java/com/saucelabs/saucerest/model/insights/Interval.java new file mode 100644 index 00000000..e36cdb40 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Interval.java @@ -0,0 +1,18 @@ +package com.saucelabs.saucerest.model.insights; + +public enum Interval { + ONE_MINUTE("1m"), + FIFTEEN_MINUTES("15m"), + ONE_HOUR("1h"), + SIX_HOURS("6h"), + TWELVE_HOURS("12h"), + ONE_DAY("1d"), + SEVEN_DAYS("7d"), + THIRTY_DAYS("30d"); + + public final String value; + + Interval(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/NameCount.java b/src/main/java/com/saucelabs/saucerest/model/insights/NameCount.java new file mode 100644 index 00000000..cdfbb0ce --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/NameCount.java @@ -0,0 +1,6 @@ +package com.saucelabs.saucerest.model.insights; + +public class NameCount { + public String name; + public Integer count; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/ParameterUtils.java b/src/main/java/com/saucelabs/saucerest/model/insights/ParameterUtils.java new file mode 100644 index 00000000..22287a31 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/ParameterUtils.java @@ -0,0 +1,18 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.Arrays; +import java.util.function.Function; + +final class ParameterUtils { + private ParameterUtils() { + } + + /** Maps an array of enum constants to their query-parameter string values, or {@code null} if the input is {@code null}. */ + static String[] valuesOf(T[] values, Function mapper) { + if (values == null) { + return null; + } + + return Arrays.stream(values).map(mapper).toArray(String[]::new); + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Scope.java b/src/main/java/com/saucelabs/saucerest/model/insights/Scope.java new file mode 100644 index 00000000..463af822 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Scope.java @@ -0,0 +1,13 @@ +package com.saucelabs.saucerest.model.insights; + +public enum Scope { + ME("me"), + ORGANIZATION("organization"), + SINGLE("single"); + + public final String value; + + Scope(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Sort.java b/src/main/java/com/saucelabs/saucerest/model/insights/Sort.java new file mode 100644 index 00000000..9b0cd04e --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Sort.java @@ -0,0 +1,12 @@ +package com.saucelabs.saucerest.model.insights; + +public enum Sort { + ASC("asc"), + DESC("desc"); + + public final String value; + + Sort(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Status.java b/src/main/java/com/saucelabs/saucerest/model/insights/Status.java new file mode 100644 index 00000000..03f5bfb9 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Status.java @@ -0,0 +1,17 @@ +package com.saucelabs.saucerest.model.insights; + +/** + * Test status values accepted by the v2 Insights endpoints (tests, test-cases, errors, trends). + */ +public enum Status { + COMPLETE("complete"), + ERROR("error"), + PASSED("passed"), + FAILED("failed"); + + public final String value; + + Status(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TagFilterMode.java b/src/main/java/com/saucelabs/saucerest/model/insights/TagFilterMode.java new file mode 100644 index 00000000..c4070ff3 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TagFilterMode.java @@ -0,0 +1,12 @@ +package com.saucelabs.saucerest.model.insights; + +public enum TagFilterMode { + AND("and"), + OR("or"); + + public final String value; + + TagFilterMode(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestCase.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestCase.java new file mode 100644 index 00000000..1b662afd --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestCase.java @@ -0,0 +1,16 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.Map; + +public class TestCase { + public String name; + public Map statuses; + public Integer totalRuns; + public Double completeRate; + public Double errorRate; + public Double failRate; + public Double passRate; + public Double avgDuration; + public Double medianDuration; + public Double totalDuration; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestCases.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestCases.java new file mode 100644 index 00000000..76e9380d --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestCases.java @@ -0,0 +1,11 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; +import java.util.Map; + +public class TestCases { + public List testCases; + public Integer total; + public Map statuses; + public Double avgRuntime; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesParameter.java new file mode 100644 index 00000000..766ad6a9 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesParameter.java @@ -0,0 +1,212 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v2/insights/{source}/test-cases}. */ +public class TestCasesParameter { + private final String orgId; + private final String userId; + private final String groupId; + private final String teamId; + private final Interval interval; + private final String start; + private final String end; + private final String[] browser; + private final String[] build; + private final String[] device; + private final String[] os; + private final Status[] status; + private final String[] tag; + private final TagFilterMode tagFilterMode; + private final Integer limit; + private final Integer offset; + private final TestCasesSortBy sortBy; + private final Sort sort; + private final String[] automationBackend; + private final DeviceGroup deviceGroup; + + private TestCasesParameter(Builder builder) { + orgId = builder.orgId; + userId = builder.userId; + groupId = builder.groupId; + teamId = builder.teamId; + interval = builder.interval; + start = builder.start; + end = builder.end; + browser = builder.browser; + build = builder.build; + device = builder.device; + os = builder.os; + status = builder.status; + tag = builder.tag; + tagFilterMode = builder.tagFilterMode; + limit = builder.limit; + offset = builder.offset; + sortBy = builder.sortBy; + sort = builder.sort; + automationBackend = builder.automationBackend; + deviceGroup = builder.deviceGroup; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("org_id", orgId), + new AbstractMap.SimpleEntry<>("user_id", userId), + new AbstractMap.SimpleEntry<>("group_id", groupId), + new AbstractMap.SimpleEntry<>("team_id", teamId), + new AbstractMap.SimpleEntry<>("interval", interval == null ? null : interval.value), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("browser", browser), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("status", ParameterUtils.valuesOf(status, s -> s.value)), + new AbstractMap.SimpleEntry<>("tag", tag), + new AbstractMap.SimpleEntry<>("tag_filter_mode", tagFilterMode == null ? null : tagFilterMode.value), + new AbstractMap.SimpleEntry<>("limit", limit), + new AbstractMap.SimpleEntry<>("offset", offset), + new AbstractMap.SimpleEntry<>("sort_by", sortBy == null ? null : sortBy.value), + new AbstractMap.SimpleEntry<>("sort", sort == null ? null : sort.value), + new AbstractMap.SimpleEntry<>("automation_backend", automationBackend), + new AbstractMap.SimpleEntry<>("device_group", deviceGroup == null ? null : deviceGroup.value) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String orgId; + private String userId; + private String groupId; + private String teamId; + private Interval interval; + private String start; + private String end; + private String[] browser; + private String[] build; + private String[] device; + private String[] os; + private Status[] status; + private String[] tag; + private TagFilterMode tagFilterMode; + private Integer limit; + private Integer offset; + private TestCasesSortBy sortBy; + private Sort sort; + private String[] automationBackend; + private DeviceGroup deviceGroup; + + /** @param orgId The organization to return results for (required). */ + public Builder(String orgId) { + this.orgId = orgId; + } + + public Builder setUserId(String val) { + userId = val; + return this; + } + + public Builder setGroupId(String val) { + groupId = val; + return this; + } + + public Builder setTeamId(String val) { + teamId = val; + return this; + } + + public Builder setInterval(Interval val) { + interval = val; + return this; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setStatus(Status[] val) { + status = val; + return this; + } + + public Builder setTag(String[] val) { + tag = val; + return this; + } + + public Builder setTagFilterMode(TagFilterMode val) { + tagFilterMode = val; + return this; + } + + public Builder setLimit(Integer val) { + limit = val; + return this; + } + + public Builder setOffset(Integer val) { + offset = val; + return this; + } + + public Builder setSortBy(TestCasesSortBy val) { + sortBy = val; + return this; + } + + public Builder setSort(Sort val) { + sort = val; + return this; + } + + public Builder setAutomationBackend(String[] val) { + automationBackend = val; + return this; + } + + public Builder setDeviceGroup(DeviceGroup val) { + deviceGroup = val; + return this; + } + + public TestCasesParameter build() { + return new TestCasesParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesSortBy.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesSortBy.java new file mode 100644 index 00000000..f7a43617 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesSortBy.java @@ -0,0 +1,23 @@ +package com.saucelabs.saucerest.model.insights; + +public enum TestCasesSortBy { + TOTAL_RUNS("total_runs"), + NAME("name"), + COMPLETE_COUNT("complete_count"), + ERROR_COUNT("error_count"), + FAIL_COUNT("fail_count"), + PASS_COUNT("pass_count"), + COMPLETE_RATE("complete_rate"), + ERROR_RATE("error_rate"), + FAILURE_RATE("failure_rate"), + PASS_RATE("pass_rate"), + AVG_DURATION("avg_duration"), + MEDIAN_DURATION("median_duration"), + TOTAL_DURATION("total_duration"); + + public final String value; + + TestCasesSortBy(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStats.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStats.java new file mode 100644 index 00000000..5fd674d3 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStats.java @@ -0,0 +1,10 @@ +package com.saucelabs.saucerest.model.insights; + +public class TestCasesStats { + public Integer consistentlyComplete; + public Integer consistentlyError; + public Integer consistentlyFailing; + public Integer consistentlyPassing; + public Integer totalTestCases; + public Integer totalTestCasesLimited; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStatsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStatsParameter.java new file mode 100644 index 00000000..76864c2a --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestCasesStatsParameter.java @@ -0,0 +1,176 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v2/insights/{source}/test-cases/stats}. */ +public class TestCasesStatsParameter { + private final String orgId; + private final String userId; + private final String groupId; + private final String teamId; + private final Interval interval; + private final String start; + private final String end; + private final String[] browser; + private final String[] build; + private final String[] device; + private final String[] os; + private final Status[] status; + private final String[] tag; + private final TagFilterMode tagFilterMode; + private final String[] automationBackend; + private final DeviceGroup deviceGroup; + + private TestCasesStatsParameter(Builder builder) { + orgId = builder.orgId; + userId = builder.userId; + groupId = builder.groupId; + teamId = builder.teamId; + interval = builder.interval; + start = builder.start; + end = builder.end; + browser = builder.browser; + build = builder.build; + device = builder.device; + os = builder.os; + status = builder.status; + tag = builder.tag; + tagFilterMode = builder.tagFilterMode; + automationBackend = builder.automationBackend; + deviceGroup = builder.deviceGroup; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("org_id", orgId), + new AbstractMap.SimpleEntry<>("user_id", userId), + new AbstractMap.SimpleEntry<>("group_id", groupId), + new AbstractMap.SimpleEntry<>("team_id", teamId), + new AbstractMap.SimpleEntry<>("interval", interval == null ? null : interval.value), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("browser", browser), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("status", ParameterUtils.valuesOf(status, s -> s.value)), + new AbstractMap.SimpleEntry<>("tag", tag), + new AbstractMap.SimpleEntry<>("tag_filter_mode", tagFilterMode == null ? null : tagFilterMode.value), + new AbstractMap.SimpleEntry<>("automation_backend", automationBackend), + new AbstractMap.SimpleEntry<>("device_group", deviceGroup == null ? null : deviceGroup.value) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String orgId; + private String userId; + private String groupId; + private String teamId; + private Interval interval; + private String start; + private String end; + private String[] browser; + private String[] build; + private String[] device; + private String[] os; + private Status[] status; + private String[] tag; + private TagFilterMode tagFilterMode; + private String[] automationBackend; + private DeviceGroup deviceGroup; + + /** @param orgId The organization to return results for (required). */ + public Builder(String orgId) { + this.orgId = orgId; + } + + public Builder setUserId(String val) { + userId = val; + return this; + } + + public Builder setGroupId(String val) { + groupId = val; + return this; + } + + public Builder setTeamId(String val) { + teamId = val; + return this; + } + + public Builder setInterval(Interval val) { + interval = val; + return this; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setStatus(Status[] val) { + status = val; + return this; + } + + public Builder setTag(String[] val) { + tag = val; + return this; + } + + public Builder setTagFilterMode(TagFilterMode val) { + tagFilterMode = val; + return this; + } + + public Builder setAutomationBackend(String[] val) { + automationBackend = val; + return this; + } + + public Builder setDeviceGroup(DeviceGroup val) { + deviceGroup = val; + return this; + } + + public TestCasesStatsParameter build() { + return new TestCasesStatsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestExecution.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestExecution.java new file mode 100644 index 00000000..5027f437 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestExecution.java @@ -0,0 +1,31 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; + +public class TestExecution { + public String id; + public String name; + public String status; + public String creationTime; + public String modificationTime; + public String error; + public Boolean passed; + public String browserNormalized; + public String osNormalized; + public String deviceName; + public String deviceGroup; + public String build; + public String automationBackend; + public Integer duration; + public List tags; + public String owner; + public String ancestor; + public String userId; + public String teamId; + public String groupId; + public String orgId; + public String startTime; + public String endTime; + public String deletionTime; + public Boolean isExpired; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestMetrics.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestMetrics.java new file mode 100644 index 00000000..7fb0b9d9 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestMetrics.java @@ -0,0 +1,6 @@ +package com.saucelabs.saucerest.model.insights; + +public class TestMetrics { + public Meta meta; + public TestMetricsAggs aggs; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsAggs.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsAggs.java new file mode 100644 index 00000000..3c4e0a6a --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsAggs.java @@ -0,0 +1,22 @@ +package com.saucelabs.saucerest.model.insights; + +import com.google.gson.annotations.SerializedName; +import java.util.Map; + +public class TestMetricsAggs { + public Integer count; + + @SerializedName("fastestRun") + public Item fastestRun; + + @SerializedName("slowestRun") + public Item slowestRun; + + public Map statuses; + + @SerializedName("totalQueueTime") + public Double totalQueueTime; + + @SerializedName("totalRunTime") + public Double totalRunTime; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsParameter.java new file mode 100644 index 00000000..80c7aacd --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestMetricsParameter.java @@ -0,0 +1,124 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v1/analytics/insights/test-metrics}. */ +public class TestMetricsParameter { + private final String query; + private final String start; + private final String end; + private final TimeRange timeRange; + private final Scope scope; + private final String[] owner; + private final TestResultParameter.Status status; + private final String[] os; + private final String[] browser; + + private TestMetricsParameter(Builder builder) { + query = builder.query; + start = builder.start; + end = builder.end; + timeRange = builder.timeRange; + scope = builder.scope; + owner = builder.owner; + status = builder.status; + os = builder.os; + browser = builder.browser; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("query", query), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("time_range", timeRange == null ? null : timeRange.toString()), + new AbstractMap.SimpleEntry<>("scope", scope == null ? null : scope.value), + new AbstractMap.SimpleEntry<>("owner", owner), + new AbstractMap.SimpleEntry<>("status", status == null ? null : status.getValue()), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("browser", browser) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String query; + private String start; + private String end; + private TimeRange timeRange; + private Scope scope; + private String[] owner; + private TestResultParameter.Status status; + private String[] os; + private String[] browser; + + /** @param query The name of the test to return metrics for (required). */ + public Builder(String query) { + this.query = query; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setTimeRange(TimeRange val) { + timeRange = val; + return this; + } + + public Builder setScope(Scope val) { + scope = val; + return this; + } + + /** Required when {@link #setScope(Scope)} is {@link Scope#SINGLE}. */ + public Builder setOwner(String[] val) { + owner = val; + return this; + } + + public Builder setStatus(TestResultParameter.Status val) { + status = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public TestMetricsParameter build() { + boolean isTimeRangeUsed = timeRange != null; + boolean isStartEndUsed = start != null && end != null; + + if (!isTimeRangeUsed && !isStartEndUsed) { + throw new IllegalStateException("Either 'time_range' or 'start' and 'end' must be set."); + } + if (isTimeRangeUsed && isStartEndUsed) { + throw new IllegalStateException("Only one of 'time_range' or 'start' and 'end' can be set, not both."); + } + + return new TestMetricsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestTrends.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestTrends.java new file mode 100644 index 00000000..07c45a80 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestTrends.java @@ -0,0 +1,10 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; +import java.util.Map; + +public class TestTrends { + public TrendsMeta meta; + public List buckets; + public Map> metrics; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Tests.java b/src/main/java/com/saucelabs/saucerest/model/insights/Tests.java new file mode 100644 index 00000000..13bf0e3f --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Tests.java @@ -0,0 +1,11 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; +import java.util.Map; + +public class Tests { + public List items; + public Integer total; + public Map statuses; + public Integer maxDuration; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestsParameter.java new file mode 100644 index 00000000..22032083 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestsParameter.java @@ -0,0 +1,212 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v2/insights/{source}/tests}. */ +public class TestsParameter { + private final String orgId; + private final String userId; + private final String groupId; + private final String teamId; + private final String[] build; + private final String[] os; + private final String[] device; + private final String[] browser; + private final String[] tag; + private final TagFilterMode tagFilterMode; + private final String[] error; + private final String name; + private final String start; + private final String end; + private final Integer limit; + private final Integer offset; + private final TestsSortBy sortBy; + private final Sort sort; + private final String[] automationBackend; + private final DeviceGroup deviceGroup; + + private TestsParameter(Builder builder) { + orgId = builder.orgId; + userId = builder.userId; + groupId = builder.groupId; + teamId = builder.teamId; + build = builder.build; + os = builder.os; + device = builder.device; + browser = builder.browser; + tag = builder.tag; + tagFilterMode = builder.tagFilterMode; + error = builder.error; + name = builder.name; + start = builder.start; + end = builder.end; + limit = builder.limit; + offset = builder.offset; + sortBy = builder.sortBy; + sort = builder.sort; + automationBackend = builder.automationBackend; + deviceGroup = builder.deviceGroup; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("org_id", orgId), + new AbstractMap.SimpleEntry<>("user_id", userId), + new AbstractMap.SimpleEntry<>("group_id", groupId), + new AbstractMap.SimpleEntry<>("team_id", teamId), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("browser", browser), + new AbstractMap.SimpleEntry<>("tag", tag), + new AbstractMap.SimpleEntry<>("tag_filter_mode", tagFilterMode == null ? null : tagFilterMode.value), + new AbstractMap.SimpleEntry<>("error", error), + new AbstractMap.SimpleEntry<>("name", name), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("limit", limit), + new AbstractMap.SimpleEntry<>("offset", offset), + new AbstractMap.SimpleEntry<>("sort_by", sortBy == null ? null : sortBy.value), + new AbstractMap.SimpleEntry<>("sort", sort == null ? null : sort.value), + new AbstractMap.SimpleEntry<>("automation_backend", automationBackend), + new AbstractMap.SimpleEntry<>("device_group", deviceGroup == null ? null : deviceGroup.value) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String orgId; + private String userId; + private String groupId; + private String teamId; + private String[] build; + private String[] os; + private String[] device; + private String[] browser; + private String[] tag; + private TagFilterMode tagFilterMode; + private String[] error; + private String name; + private String start; + private String end; + private Integer limit; + private Integer offset; + private TestsSortBy sortBy; + private Sort sort; + private String[] automationBackend; + private DeviceGroup deviceGroup; + + /** @param orgId The organization to return results for (required). */ + public Builder(String orgId) { + this.orgId = orgId; + } + + public Builder setUserId(String val) { + userId = val; + return this; + } + + public Builder setGroupId(String val) { + groupId = val; + return this; + } + + public Builder setTeamId(String val) { + teamId = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public Builder setTag(String[] val) { + tag = val; + return this; + } + + public Builder setTagFilterMode(TagFilterMode val) { + tagFilterMode = val; + return this; + } + + public Builder setError(String[] val) { + error = val; + return this; + } + + public Builder setName(String val) { + name = val; + return this; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setLimit(Integer val) { + limit = val; + return this; + } + + public Builder setOffset(Integer val) { + offset = val; + return this; + } + + public Builder setSortBy(TestsSortBy val) { + sortBy = val; + return this; + } + + public Builder setSort(Sort val) { + sort = val; + return this; + } + + public Builder setAutomationBackend(String[] val) { + automationBackend = val; + return this; + } + + public Builder setDeviceGroup(DeviceGroup val) { + deviceGroup = val; + return this; + } + + public TestsParameter build() { + return new TestsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TestsSortBy.java b/src/main/java/com/saucelabs/saucerest/model/insights/TestsSortBy.java new file mode 100644 index 00000000..fbc98535 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TestsSortBy.java @@ -0,0 +1,12 @@ +package com.saucelabs.saucerest.model.insights; + +public enum TestsSortBy { + DURATION("duration"), + CREATION_TIME("creation_time"); + + public final String value; + + TestsSortBy(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TimeRange.java b/src/main/java/com/saucelabs/saucerest/model/insights/TimeRange.java new file mode 100644 index 00000000..5b247d26 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TimeRange.java @@ -0,0 +1,17 @@ +package com.saucelabs.saucerest.model.insights; + +/** A relative time window, measured backward from now, e.g. {@code new TimeRange(7, Unit.D)} for the last 7 days. */ +public class TimeRange { + private final int value; + private final Unit unit; + + public TimeRange(int value, Unit unit) { + this.value = value; + this.unit = unit; + } + + @Override + public String toString() { + return String.format("%d%s", value, unit.value); + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TrendBucket.java b/src/main/java/com/saucelabs/saucerest/model/insights/TrendBucket.java new file mode 100644 index 00000000..9dfd2b88 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TrendBucket.java @@ -0,0 +1,8 @@ +package com.saucelabs.saucerest.model.insights; + +public class TrendBucket { + public Long timestamp; + public String datetime; + public Integer count; + public Aggs aggs; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrors.java b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrors.java new file mode 100644 index 00000000..e4069721 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrors.java @@ -0,0 +1,9 @@ +package com.saucelabs.saucerest.model.insights; + +import java.util.List; + +public class TrendsErrors { + public TrendsMeta meta; + public List buckets; + public Integer allItemsCount; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrorsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrorsParameter.java new file mode 100644 index 00000000..8498dd1d --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsErrorsParameter.java @@ -0,0 +1,176 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v2/insights/{source}/trends/errors}. */ +public class TrendsErrorsParameter { + private final String orgId; + private final Interval interval; + private final String userId; + private final String groupId; + private final String teamId; + private final String start; + private final String end; + private final String[] browser; + private final String[] build; + private final String[] device; + private final String[] os; + private final Status[] status; + private final String[] tag; + private final TagFilterMode tagFilterMode; + private final String[] automationBackend; + private final DeviceGroup deviceGroup; + + private TrendsErrorsParameter(Builder builder) { + orgId = builder.orgId; + interval = builder.interval; + userId = builder.userId; + groupId = builder.groupId; + teamId = builder.teamId; + start = builder.start; + end = builder.end; + browser = builder.browser; + build = builder.build; + device = builder.device; + os = builder.os; + status = builder.status; + tag = builder.tag; + tagFilterMode = builder.tagFilterMode; + automationBackend = builder.automationBackend; + deviceGroup = builder.deviceGroup; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("org_id", orgId), + new AbstractMap.SimpleEntry<>("interval", interval == null ? null : interval.value), + new AbstractMap.SimpleEntry<>("user_id", userId), + new AbstractMap.SimpleEntry<>("group_id", groupId), + new AbstractMap.SimpleEntry<>("team_id", teamId), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("browser", browser), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("status", ParameterUtils.valuesOf(status, s -> s.value)), + new AbstractMap.SimpleEntry<>("tag", tag), + new AbstractMap.SimpleEntry<>("tag_filter_mode", tagFilterMode == null ? null : tagFilterMode.value), + new AbstractMap.SimpleEntry<>("automation_backend", automationBackend), + new AbstractMap.SimpleEntry<>("device_group", deviceGroup == null ? null : deviceGroup.value) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String orgId; + private Interval interval; + private String userId; + private String groupId; + private String teamId; + private String start; + private String end; + private String[] browser; + private String[] build; + private String[] device; + private String[] os; + private Status[] status; + private String[] tag; + private TagFilterMode tagFilterMode; + private String[] automationBackend; + private DeviceGroup deviceGroup; + + /** @param orgId The organization to return results for (required). */ + public Builder(String orgId) { + this.orgId = orgId; + } + + public Builder setInterval(Interval val) { + interval = val; + return this; + } + + public Builder setUserId(String val) { + userId = val; + return this; + } + + public Builder setGroupId(String val) { + groupId = val; + return this; + } + + public Builder setTeamId(String val) { + teamId = val; + return this; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setStatus(Status[] val) { + status = val; + return this; + } + + public Builder setTag(String[] val) { + tag = val; + return this; + } + + public Builder setTagFilterMode(TagFilterMode val) { + tagFilterMode = val; + return this; + } + + public Builder setAutomationBackend(String[] val) { + automationBackend = val; + return this; + } + + public Builder setDeviceGroup(DeviceGroup val) { + deviceGroup = val; + return this; + } + + public TrendsErrorsParameter build() { + return new TrendsErrorsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TrendsMeta.java b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsMeta.java new file mode 100644 index 00000000..731cb711 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsMeta.java @@ -0,0 +1,5 @@ +package com.saucelabs.saucerest.model.insights; + +public class TrendsMeta { + public String status; +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/TrendsTestsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsTestsParameter.java new file mode 100644 index 00000000..28210eb3 --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/TrendsTestsParameter.java @@ -0,0 +1,186 @@ +package com.saucelabs.saucerest.model.insights; + +import java.time.LocalDateTime; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** Query parameters for {@code GET /v2/insights/{source}/trends/tests}. */ +public class TrendsTestsParameter { + private final String orgId; + private final Interval interval; + private final String timeZone; + private final String userId; + private final String groupId; + private final String teamId; + private final String start; + private final String end; + private final String[] browser; + private final String[] build; + private final String[] device; + private final String[] os; + private final Status[] status; + private final String[] tag; + private final TagFilterMode tagFilterMode; + private final String[] automationBackend; + private final DeviceGroup deviceGroup; + + private TrendsTestsParameter(Builder builder) { + orgId = builder.orgId; + interval = builder.interval; + timeZone = builder.timeZone; + userId = builder.userId; + groupId = builder.groupId; + teamId = builder.teamId; + start = builder.start; + end = builder.end; + browser = builder.browser; + build = builder.build; + device = builder.device; + os = builder.os; + status = builder.status; + tag = builder.tag; + tagFilterMode = builder.tagFilterMode; + automationBackend = builder.automationBackend; + deviceGroup = builder.deviceGroup; + } + + public Map toMap() { + Map parameters = new HashMap<>(); + + Stream.of( + new AbstractMap.SimpleEntry<>("org_id", orgId), + new AbstractMap.SimpleEntry<>("interval", interval == null ? null : interval.value), + new AbstractMap.SimpleEntry<>("time_zone", timeZone), + new AbstractMap.SimpleEntry<>("user_id", userId), + new AbstractMap.SimpleEntry<>("group_id", groupId), + new AbstractMap.SimpleEntry<>("team_id", teamId), + new AbstractMap.SimpleEntry<>("start", start), + new AbstractMap.SimpleEntry<>("end", end), + new AbstractMap.SimpleEntry<>("browser", browser), + new AbstractMap.SimpleEntry<>("build", build), + new AbstractMap.SimpleEntry<>("device", device), + new AbstractMap.SimpleEntry<>("os", os), + new AbstractMap.SimpleEntry<>("status", ParameterUtils.valuesOf(status, s -> s.value)), + new AbstractMap.SimpleEntry<>("tag", tag), + new AbstractMap.SimpleEntry<>("tag_filter_mode", tagFilterMode == null ? null : tagFilterMode.value), + new AbstractMap.SimpleEntry<>("automation_backend", automationBackend), + new AbstractMap.SimpleEntry<>("device_group", deviceGroup == null ? null : deviceGroup.value) + ) + .filter(e -> e.getValue() != null) + .forEach(e -> parameters.put(e.getKey(), e.getValue())); + + return parameters; + } + + public static final class Builder { + private final String orgId; + private Interval interval; + private String timeZone; + private String userId; + private String groupId; + private String teamId; + private String start; + private String end; + private String[] browser; + private String[] build; + private String[] device; + private String[] os; + private Status[] status; + private String[] tag; + private TagFilterMode tagFilterMode; + private String[] automationBackend; + private DeviceGroup deviceGroup; + + /** @param orgId The organization to return results for (required). */ + public Builder(String orgId) { + this.orgId = orgId; + } + + public Builder setInterval(Interval val) { + interval = val; + return this; + } + + /** @param val UTC offset, e.g. {@code "+01:00"} (default: {@code "+00:00"}). */ + public Builder setTimeZone(String val) { + timeZone = val; + return this; + } + + public Builder setUserId(String val) { + userId = val; + return this; + } + + public Builder setGroupId(String val) { + groupId = val; + return this; + } + + public Builder setTeamId(String val) { + teamId = val; + return this; + } + + public Builder setStart(LocalDateTime val) { + start = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setEnd(LocalDateTime val) { + end = DateTimeUtils.toUtcString(val); + return this; + } + + public Builder setBrowser(String[] val) { + browser = val; + return this; + } + + public Builder setBuild(String[] val) { + build = val; + return this; + } + + public Builder setDevice(String[] val) { + device = val; + return this; + } + + public Builder setOs(String[] val) { + os = val; + return this; + } + + public Builder setStatus(Status[] val) { + status = val; + return this; + } + + public Builder setTag(String[] val) { + tag = val; + return this; + } + + public Builder setTagFilterMode(TagFilterMode val) { + tagFilterMode = val; + return this; + } + + public Builder setAutomationBackend(String[] val) { + automationBackend = val; + return this; + } + + public Builder setDeviceGroup(DeviceGroup val) { + deviceGroup = val; + return this; + } + + public TrendsTestsParameter build() { + return new TrendsTestsParameter(this); + } + } +} diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/Unit.java b/src/main/java/com/saucelabs/saucerest/model/insights/Unit.java new file mode 100644 index 00000000..21c5366f --- /dev/null +++ b/src/main/java/com/saucelabs/saucerest/model/insights/Unit.java @@ -0,0 +1,14 @@ +package com.saucelabs.saucerest.model.insights; + +public enum Unit { + D("d"), + H("h"), + M("m"), + S("s"); + + public final String value; + + Unit(String value) { + this.value = value; + } +} diff --git a/src/test/java/com/saucelabs/saucerest/integration/InsightsEndpointTest.java b/src/test/java/com/saucelabs/saucerest/integration/InsightsEndpointTest.java index 470a49f5..3b0a6336 100644 --- a/src/test/java/com/saucelabs/saucerest/integration/InsightsEndpointTest.java +++ b/src/test/java/com/saucelabs/saucerest/integration/InsightsEndpointTest.java @@ -1,18 +1,41 @@ package com.saucelabs.saucerest.integration; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.saucelabs.saucerest.DataCenter; +import com.saucelabs.saucerest.JobSource; import com.saucelabs.saucerest.SauceREST; +import com.saucelabs.saucerest.api.AccountsEndpoint; import com.saucelabs.saucerest.api.InsightsEndpoint; +import com.saucelabs.saucerest.model.insights.AnalyticsTrendsParameter; +import com.saucelabs.saucerest.model.insights.Errors; +import com.saucelabs.saucerest.model.insights.ErrorsParameter; +import com.saucelabs.saucerest.model.insights.TestCases; +import com.saucelabs.saucerest.model.insights.TestCasesParameter; +import com.saucelabs.saucerest.model.insights.TestCasesStats; +import com.saucelabs.saucerest.model.insights.TestCasesStatsParameter; +import com.saucelabs.saucerest.model.insights.TestMetrics; +import com.saucelabs.saucerest.model.insights.TestMetricsParameter; import com.saucelabs.saucerest.model.insights.TestResult; import com.saucelabs.saucerest.model.insights.TestResultParameter; +import com.saucelabs.saucerest.model.insights.Tests; +import com.saucelabs.saucerest.model.insights.TestTrends; +import com.saucelabs.saucerest.model.insights.TestsParameter; +import com.saucelabs.saucerest.model.insights.TrendsErrors; +import com.saucelabs.saucerest.model.insights.TrendsErrorsParameter; +import com.saucelabs.saucerest.model.insights.TrendsTestsParameter; import java.io.IOException; import java.time.LocalDateTime; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; public class InsightsEndpointTest { + private static String getOrgId(SauceREST sauceREST) throws IOException { + AccountsEndpoint accountsEndpoint = sauceREST.getAccountsEndpoint(); + return accountsEndpoint.getOrganization().results.get(0).id; + } + @ParameterizedTest @EnumSource(DataCenter.class) public void getTestResultTest(DataCenter dataCenter) throws IOException { @@ -30,4 +53,124 @@ public void getTestResultTest(DataCenter dataCenter) throws IOException { assertTrue(testResult.items.size() > 0); } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTestMetricsSummaryTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + TestMetricsParameter parameter = new TestMetricsParameter.Builder("AnalyticsSeleniumTest") + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + TestMetrics testMetrics = insightsEndpoint.getTestMetricsSummary(parameter); + + assertNotNull(testMetrics.aggs); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTestTrendsTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + AnalyticsTrendsParameter parameter = new AnalyticsTrendsParameter.Builder() + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + TestTrends testTrends = insightsEndpoint.getTestTrends(parameter); + + assertNotNull(testTrends.buckets); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTestsTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + TestsParameter parameter = new TestsParameter.Builder(getOrgId(sauceREST)) + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + Tests tests = insightsEndpoint.getTests(JobSource.VDC, parameter); + + assertNotNull(tests.items); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTestCasesTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + TestCasesParameter parameter = new TestCasesParameter.Builder(getOrgId(sauceREST)) + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + TestCases testCases = insightsEndpoint.getTestCases(JobSource.VDC, parameter); + + assertNotNull(testCases.testCases); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTestCasesStatsTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + TestCasesStatsParameter parameter = new TestCasesStatsParameter.Builder(getOrgId(sauceREST)) + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + TestCasesStats testCasesStats = insightsEndpoint.getTestCasesStats(JobSource.VDC, parameter); + + assertNotNull(testCasesStats); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getErrorsTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + ErrorsParameter parameter = new ErrorsParameter.Builder(getOrgId(sauceREST)) + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + Errors errors = insightsEndpoint.getErrors(JobSource.VDC, parameter); + + assertNotNull(errors.buckets); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTrendsTestsTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + TrendsTestsParameter parameter = new TrendsTestsParameter.Builder(getOrgId(sauceREST)) + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + TestTrends testTrends = insightsEndpoint.getTrendsTests(JobSource.VDC, parameter); + + assertNotNull(testTrends.buckets); + } + + @ParameterizedTest + @EnumSource(DataCenter.class) + public void getTrendsErrorsTest(DataCenter dataCenter) throws IOException { + SauceREST sauceREST = new SauceREST(dataCenter); + InsightsEndpoint insightsEndpoint = sauceREST.getInsightsEndpoint(); + + TrendsErrorsParameter parameter = new TrendsErrorsParameter.Builder(getOrgId(sauceREST)) + .setStart(LocalDateTime.now().minusDays(30)) + .setEnd(LocalDateTime.now()) + .build(); + TrendsErrors trendsErrors = insightsEndpoint.getTrendsErrors(JobSource.VDC, parameter); + + assertNotNull(trendsErrors.buckets); + } } \ No newline at end of file diff --git a/src/test/java/com/saucelabs/saucerest/unit/AnalyticsTrendsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/AnalyticsTrendsParameterTest.java new file mode 100644 index 00000000..dee4b014 --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/AnalyticsTrendsParameterTest.java @@ -0,0 +1,31 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.saucelabs.saucerest.model.insights.AnalyticsTrendsParameter; +import com.saucelabs.saucerest.model.insights.Interval; +import com.saucelabs.saucerest.model.insights.TimeRange; +import com.saucelabs.saucerest.model.insights.Unit; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class AnalyticsTrendsParameterTest { + + @Test + public void testToMapWithTimeRange() { + AnalyticsTrendsParameter params = new AnalyticsTrendsParameter.Builder() + .setTimeRange(new TimeRange(1, Unit.H)) + .setInterval(Interval.FIFTEEN_MINUTES) + .build(); + + Map map = params.toMap(); + assertEquals("1h", map.get("time_range")); + assertEquals("15m", map.get("interval")); + } + + @Test + public void testBuildThrowsWhenNeitherTimeRangeNorStartEndSet() { + assertThrows(IllegalStateException.class, () -> new AnalyticsTrendsParameter.Builder().build()); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/ErrorsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/ErrorsParameterTest.java new file mode 100644 index 00000000..920c5f5c --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/ErrorsParameterTest.java @@ -0,0 +1,37 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.saucelabs.saucerest.model.insights.ErrorsParameter; +import com.saucelabs.saucerest.model.insights.Interval; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class ErrorsParameterTest { + + @Test + public void testToMap() { + ErrorsParameter params = new ErrorsParameter.Builder("org123") + .setInterval(Interval.ONE_HOUR) + .setUserId("user123") + .setLimit(10) + .setOffset(0) + .build(); + + Map map = params.toMap(); + assertEquals("org123", map.get("org_id")); + assertEquals("1h", map.get("interval")); + assertEquals("user123", map.get("user_id")); + assertEquals(10, map.get("limit")); + assertEquals(0, map.get("offset")); + } + + @Test + public void testToMapWithOnlyRequiredValues() { + ErrorsParameter params = new ErrorsParameter.Builder("org123").build(); + + Map map = params.toMap(); + assertEquals(1, map.size()); + assertEquals("org123", map.get("org_id")); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/TestCasesParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/TestCasesParameterTest.java new file mode 100644 index 00000000..903b8b91 --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/TestCasesParameterTest.java @@ -0,0 +1,45 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.saucelabs.saucerest.model.insights.Interval; +import com.saucelabs.saucerest.model.insights.Sort; +import com.saucelabs.saucerest.model.insights.Status; +import com.saucelabs.saucerest.model.insights.TestCasesParameter; +import com.saucelabs.saucerest.model.insights.TestCasesSortBy; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TestCasesParameterTest { + + @Test + public void testToMap() { + TestCasesParameter params = new TestCasesParameter.Builder("org123") + .setInterval(Interval.SEVEN_DAYS) + .setStatus(new Status[] {Status.PASSED, Status.FAILED}) + .setLimit(25) + .setOffset(5) + .setSortBy(TestCasesSortBy.AVG_DURATION) + .setSort(Sort.DESC) + .build(); + + Map map = params.toMap(); + assertEquals("org123", map.get("org_id")); + assertEquals("7d", map.get("interval")); + assertArrayEquals(new String[] {"passed", "failed"}, (String[]) map.get("status")); + assertEquals(25, map.get("limit")); + assertEquals(5, map.get("offset")); + assertEquals("avg_duration", map.get("sort_by")); + assertEquals("desc", map.get("sort")); + } + + @Test + public void testToMapWithOnlyRequiredValues() { + TestCasesParameter params = new TestCasesParameter.Builder("org123").build(); + + Map map = params.toMap(); + assertEquals(1, map.size()); + assertEquals("org123", map.get("org_id")); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/TestCasesStatsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/TestCasesStatsParameterTest.java new file mode 100644 index 00000000..1aea4598 --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/TestCasesStatsParameterTest.java @@ -0,0 +1,34 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.saucelabs.saucerest.model.insights.DeviceGroup; +import com.saucelabs.saucerest.model.insights.Interval; +import com.saucelabs.saucerest.model.insights.TestCasesStatsParameter; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TestCasesStatsParameterTest { + + @Test + public void testToMap() { + TestCasesStatsParameter params = new TestCasesStatsParameter.Builder("org123") + .setInterval(Interval.ONE_DAY) + .setDeviceGroup(DeviceGroup.PRIVATE) + .build(); + + Map map = params.toMap(); + assertEquals("org123", map.get("org_id")); + assertEquals("1d", map.get("interval")); + assertEquals("private", map.get("device_group")); + } + + @Test + public void testToMapWithOnlyRequiredValues() { + TestCasesStatsParameter params = new TestCasesStatsParameter.Builder("org123").build(); + + Map map = params.toMap(); + assertEquals(1, map.size()); + assertEquals("org123", map.get("org_id")); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/TestMetricsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/TestMetricsParameterTest.java new file mode 100644 index 00000000..3548fbe4 --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/TestMetricsParameterTest.java @@ -0,0 +1,61 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.saucelabs.saucerest.model.insights.Scope; +import com.saucelabs.saucerest.model.insights.TestMetricsParameter; +import com.saucelabs.saucerest.model.insights.TestResultParameter; +import com.saucelabs.saucerest.model.insights.TimeRange; +import com.saucelabs.saucerest.model.insights.Unit; +import java.time.LocalDateTime; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TestMetricsParameterTest { + + @Test + public void testToMapWithStartAndEnd() { + TestMetricsParameter params = new TestMetricsParameter.Builder("my test") + .setStart(LocalDateTime.of(2024, 1, 1, 0, 0, 0)) + .setEnd(LocalDateTime.of(2024, 1, 2, 0, 0, 0)) + .setScope(Scope.ORGANIZATION) + .setStatus(TestResultParameter.Status.PASSED) + .build(); + + Map map = params.toMap(); + assertEquals("my test", map.get("query")); + assertEquals("organization", map.get("scope")); + assertEquals("passed", map.get("status")); + assertTrue(((String) map.get("start")).matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")); + assertTrue(((String) map.get("end")).matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")); + } + + @Test + public void testToMapWithTimeRange() { + TestMetricsParameter params = new TestMetricsParameter.Builder("my test") + .setTimeRange(new TimeRange(7, Unit.D)) + .build(); + + Map map = params.toMap(); + assertEquals("my test", map.get("query")); + assertEquals("7d", map.get("time_range")); + } + + @Test + public void testBuildThrowsWhenNeitherTimeRangeNorStartEndSet() { + assertThrows(IllegalStateException.class, () -> new TestMetricsParameter.Builder("my test").build()); + } + + @Test + public void testBuildThrowsWhenBothTimeRangeAndStartEndSet() { + assertThrows( + IllegalStateException.class, + () -> new TestMetricsParameter.Builder("my test") + .setStart(LocalDateTime.of(2024, 1, 1, 0, 0, 0)) + .setEnd(LocalDateTime.of(2024, 1, 2, 0, 0, 0)) + .setTimeRange(new TimeRange(7, Unit.D)) + .build()); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/TestsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/TestsParameterTest.java new file mode 100644 index 00000000..300740e2 --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/TestsParameterTest.java @@ -0,0 +1,65 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.saucelabs.saucerest.model.insights.DeviceGroup; +import com.saucelabs.saucerest.model.insights.Sort; +import com.saucelabs.saucerest.model.insights.TagFilterMode; +import com.saucelabs.saucerest.model.insights.TestsParameter; +import com.saucelabs.saucerest.model.insights.TestsSortBy; +import java.time.LocalDateTime; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TestsParameterTest { + + @Test + public void testToMap() { + TestsParameter params = new TestsParameter.Builder("org123") + .setUserId("user123") + .setGroupId("group123") + .setTeamId("team123") + .setBuild(new String[] {"build1"}) + .setOs(new String[] {"Windows 10"}) + .setDevice(new String[] {"iPhone"}) + .setBrowser(new String[] {"chrome"}) + .setTag(new String[] {"smoke"}) + .setTagFilterMode(TagFilterMode.AND) + .setError(new String[] {"timeout"}) + .setName("my test") + .setStart(LocalDateTime.of(2024, 1, 1, 0, 0, 0)) + .setEnd(LocalDateTime.of(2024, 1, 2, 0, 0, 0)) + .setLimit(50) + .setOffset(0) + .setSortBy(TestsSortBy.DURATION) + .setSort(Sort.ASC) + .setAutomationBackend(new String[] {"webdriver"}) + .setDeviceGroup(DeviceGroup.PUBLIC) + .build(); + + Map map = params.toMap(); + assertEquals("org123", map.get("org_id")); + assertEquals("user123", map.get("user_id")); + assertEquals("group123", map.get("group_id")); + assertEquals("team123", map.get("team_id")); + assertEquals("and", map.get("tag_filter_mode")); + assertEquals("my test", map.get("name")); + assertEquals(50, map.get("limit")); + assertEquals(0, map.get("offset")); + assertEquals("duration", map.get("sort_by")); + assertEquals("asc", map.get("sort")); + assertEquals("public", map.get("device_group")); + assertTrue(((String) map.get("start")).matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")); + assertTrue(((String) map.get("end")).matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")); + } + + @Test + public void testToMapWithOnlyRequiredValues() { + TestsParameter params = new TestsParameter.Builder("org123").build(); + + Map map = params.toMap(); + assertEquals(1, map.size()); + assertEquals("org123", map.get("org_id")); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/TrendsErrorsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/TrendsErrorsParameterTest.java new file mode 100644 index 00000000..b5851ade --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/TrendsErrorsParameterTest.java @@ -0,0 +1,33 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.saucelabs.saucerest.model.insights.Interval; +import com.saucelabs.saucerest.model.insights.TrendsErrorsParameter; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TrendsErrorsParameterTest { + + @Test + public void testToMap() { + TrendsErrorsParameter params = new TrendsErrorsParameter.Builder("org123") + .setInterval(Interval.THIRTY_DAYS) + .setTeamId("team123") + .build(); + + Map map = params.toMap(); + assertEquals("org123", map.get("org_id")); + assertEquals("30d", map.get("interval")); + assertEquals("team123", map.get("team_id")); + } + + @Test + public void testToMapWithOnlyRequiredValues() { + TrendsErrorsParameter params = new TrendsErrorsParameter.Builder("org123").build(); + + Map map = params.toMap(); + assertEquals(1, map.size()); + assertEquals("org123", map.get("org_id")); + } +} diff --git a/src/test/java/com/saucelabs/saucerest/unit/TrendsTestsParameterTest.java b/src/test/java/com/saucelabs/saucerest/unit/TrendsTestsParameterTest.java new file mode 100644 index 00000000..d986c6c2 --- /dev/null +++ b/src/test/java/com/saucelabs/saucerest/unit/TrendsTestsParameterTest.java @@ -0,0 +1,33 @@ +package com.saucelabs.saucerest.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.saucelabs.saucerest.model.insights.Interval; +import com.saucelabs.saucerest.model.insights.TrendsTestsParameter; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TrendsTestsParameterTest { + + @Test + public void testToMap() { + TrendsTestsParameter params = new TrendsTestsParameter.Builder("org123") + .setInterval(Interval.SIX_HOURS) + .setTimeZone("+01:00") + .build(); + + Map map = params.toMap(); + assertEquals("org123", map.get("org_id")); + assertEquals("6h", map.get("interval")); + assertEquals("+01:00", map.get("time_zone")); + } + + @Test + public void testToMapWithOnlyRequiredValues() { + TrendsTestsParameter params = new TrendsTestsParameter.Builder("org123").build(); + + Map map = params.toMap(); + assertEquals(1, map.size()); + assertEquals("org123", map.get("org_id")); + } +} From cf560ab2f6a33e67a63361a4e3f701d8f815c951 Mon Sep 17 00:00:00 2001 From: Diego Molina Date: Wed, 16 Sep 2026 15:49:49 +0200 Subject: [PATCH 2/2] Remove unnecessary no-arg constructor flagged by Codacy Builder's explicit public no-arg constructor was identical to the one the compiler generates by default. Co-Authored-By: Claude Sonnet 5 --- .../saucerest/model/insights/AnalyticsTrendsParameter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java b/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java index d72076e9..2d85bc08 100644 --- a/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java +++ b/src/main/java/com/saucelabs/saucerest/model/insights/AnalyticsTrendsParameter.java @@ -73,9 +73,6 @@ public static final class Builder { private String[] tag; private TagFilterMode tagFilterMode; - public Builder() { - } - public Builder setStart(LocalDateTime val) { start = DateTimeUtils.toUtcString(val); return this;