diff --git a/build.gradle b/build.gradle index 86855ae..2698253 100644 --- a/build.gradle +++ b/build.gradle @@ -42,12 +42,25 @@ dependencies { implementation 'com.epam.reportportal:plugin-api' annotationProcessor 'com.epam.reportportal:plugin-api' } else { - implementation 'com.github.reportportal:commons-dao:7de2ad1' - implementation 'com.github.reportportal:plugin-api:9bc4735' - annotationProcessor 'com.github.reportportal:plugin-api:9bc4735' + implementation 'com.github.reportportal:commons-dao:28d0461' + implementation 'com.github.reportportal:plugin-api:a9a8b73' + annotationProcessor 'com.github.reportportal:plugin-api:a9a8b73' } implementation 'org.hibernate:hibernate-core:5.6.15.Final' - implementation 'com.saucelabs:saucerest:1.0.43' + + // TODO: 2.5.3+ switched to camel-case models. UI updates required + implementation 'com.saucelabs:saucerest:2.5.1' + implementation 'dev.failsafe:failsafe:3.3.2' + implementation 'org.awaitility:awaitility:4.2.2' + implementation 'org.hamcrest:hamcrest-core:2.2' + runtimeOnly 'com.squareup.moshi:moshi:1.15.1' + + // add lombok support + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testCompileOnly "org.projectlombok:lombok:${lombokVersion}" + testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + } generatePomFileForShadowPublication { pom.packaging = "jar" } @@ -69,8 +82,13 @@ shadowJar { archiveClassifier.set(null) zip64 true dependencies { - include(dependency('com.saucelabs:saucerest:1.0.43')) - include(dependency('org.json:json:20171018')) + include(dependency('com.saucelabs:saucerest:2.5.1')) + include(dependency('org.json:json:20240303')) + include(dependency('org.awaitility:awaitility:4.2.2')) + include(dependency('org.hamcrest:hamcrest:2.2')) + include(dependency('com.squareup.moshi:moshi:1.15.1')) + include(dependency('net.jodah:failsafe:2.4.4')) + } } diff --git a/gradle.properties b/gradle.properties index 0cfca58..4c492f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ -version=5.11.0 \ No newline at end of file +version=5.11.0 +lombokVersion=1.18.34 diff --git a/src/main/java/com/epam/reportportal/saucelabs/AssetsCommand.java b/src/main/java/com/epam/reportportal/saucelabs/AssetsCommand.java index 9f6e8b7..4202dfc 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/AssetsCommand.java +++ b/src/main/java/com/epam/reportportal/saucelabs/AssetsCommand.java @@ -17,51 +17,79 @@ package com.epam.reportportal.saucelabs; import static com.epam.reportportal.saucelabs.SaucelabsExtension.JOB_ID; -import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; import com.epam.reportportal.extension.PluginCommand; -import com.epam.reportportal.rules.commons.validation.Suppliers; -import com.epam.ta.reportportal.entity.integration.Integration; -import com.epam.reportportal.rules.exception.ReportPortalException; import com.epam.reportportal.rules.exception.ErrorType; +import com.epam.reportportal.rules.exception.ReportPortalException; +import com.epam.reportportal.saucelabs.client.RestClientBuilder; +import com.epam.reportportal.saucelabs.model.SauceProperties; +import com.epam.ta.reportportal.entity.integration.Integration; import com.fasterxml.jackson.databind.ObjectMapper; -import com.saucelabs.saucerest.SauceREST; -import java.io.IOException; +import com.saucelabs.saucerest.MoshiSingleton; +import com.saucelabs.saucerest.model.jobs.JobAssets; +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; import java.util.Map; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; /** * @author Pavel Bortnik */ +@Slf4j public class AssetsCommand implements PluginCommand { - private final RestClient restClient; + private final RestClientBuilder restClient; - public AssetsCommand(RestClient restClient) { + + public AssetsCommand(RestClientBuilder restClient) { this.restClient = restClient; } + @SneakyThrows @Override public Object executeCommand(Integration integration, Map params) { ValidationUtils.validateParams(params); - SauceREST sauce = - restClient.buildSauceClient(integration, (String) params.get(DATA_CENTER.getName())); - String jobId = (String) params.get(JOB_ID); - String assetsPrefix = - sauce.getAppServer() + "rest/v1/" + sauce.getUsername() + "/jobs/" + jobId + "/assets/"; + ValidationUtils.validateIntegrationParams(integration.getParams()); + + SauceProperties sp = new SauceProperties(integration.getParams().getParams()); + sp.setJobId((String) params.get(JOB_ID)); + RestTemplate restTemplate = restClient.buildRestTemplate(sp); + try { - String content = sauce.retrieveResults(sauce.getUsername() + "/jobs/" + jobId + "/assets"); - if (StringUtils.isEmpty(content)) { - throw new ReportPortalException( - ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - Suppliers.formattedSupplier("Job '{}' not found.", jobId) - ); + String url = "/rest/v1/" + sp.getUsername() + "/jobs/" + sp.getJobId() + "/assets"; + String jobAssets = restTemplate.getForObject(url, String.class); + + JSONObject response = new JSONObject(jobAssets); + response.put("assetsPrefix", + sp.getDatacenter().apiServer + "rest/v1/" + sp.getUsername() + "/jobs/" + sp.getJobId() + + "/assets"); + return new ObjectMapper().readValue(response.toString(), Object.class); + + } catch (HttpClientErrorException httpException) { + if (httpException.getStatusCode().is4xxClientError()) { + // TODO: handle RD endpoint in a separate plugin command. UI updates required + //String url = sp.getDatacenter().apiServer + "v1/rdc/jobs/" + sp.getJobId(); + //DeviceJob deviceJob = restTemplate.getForObject(url, DeviceJob.class); + + JSONObject response = new JSONObject(); + response.put("assetsPrefix", + String.format("%sv1/rdc/jobs/%s/", sp.getDatacenter().apiServer, sp.getJobId())); + response.put("screenshots", new JSONArray()); + response.put("sauce-log", + String.format("%sv1/rdc/jobs/%s/deviceLogs", sp.getDatacenter().apiServer, + sp.getJobId())); + return new ObjectMapper().readValue(response.toString(), Object.class); + + } else { + throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + StringUtils.normalizeSpace("Failed to retrieve job assets")); } - Map result = new ObjectMapper().readValue(content, Map.class); - result.put("assetsPrefix", assetsPrefix); - return result; - } catch (IOException e) { - throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, e.getMessage()); } } @@ -69,4 +97,12 @@ public Object executeCommand(Integration integration, Map params public String getName() { return "assets"; } + + + public String toJson(JobAssets jobAssets) { + Moshi moshi = MoshiSingleton.getInstance(); + + JsonAdapter jsonAdapter = moshi.adapter(JobAssets.class).nonNull(); + return jsonAdapter.toJson(jobAssets); + } } diff --git a/src/main/java/com/epam/reportportal/saucelabs/GenerateAuthTokenCommand.java b/src/main/java/com/epam/reportportal/saucelabs/GenerateAuthTokenCommand.java index 720eae3..998c6bf 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/GenerateAuthTokenCommand.java +++ b/src/main/java/com/epam/reportportal/saucelabs/GenerateAuthTokenCommand.java @@ -3,12 +3,12 @@ import static com.epam.reportportal.saucelabs.SaucelabsExtension.JOB_ID; import static com.epam.reportportal.saucelabs.SaucelabsProperties.ACCESS_TOKEN; import static com.epam.reportportal.saucelabs.SaucelabsProperties.USERNAME; +import static com.epam.reportportal.saucelabs.ValidationUtils.validateIntegrationParams; import static com.epam.reportportal.saucelabs.ValidationUtils.validateParams; import com.epam.reportportal.extension.PluginCommand; -import com.epam.ta.reportportal.entity.integration.Integration; import com.epam.reportportal.rules.exception.ReportPortalException; -import com.epam.reportportal.rules.exception.ErrorType; +import com.epam.ta.reportportal.entity.integration.Integration; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; @@ -32,16 +32,10 @@ public GenerateAuthTokenCommand(BasicTextEncryptor textEncryptor) { public Object executeCommand(Integration integration, Map params) { try { validateParams(params); + validateIntegrationParams(integration.getParams()); - String username = USERNAME.getParam(integration.getParams()).orElseThrow( - () -> new ReportPortalException( - ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - "Username is not specified." - )); - String accessToken = textEncryptor.decrypt(ACCESS_TOKEN.getParam(integration.getParams()) - .orElseThrow(() -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - "Access token is not specified." - ))); + String username = USERNAME.getParam(integration.getParams()); + String accessToken = textEncryptor.decrypt(ACCESS_TOKEN.getParam(integration.getParams())); SecretKeySpec keySpec = new SecretKeySpec((username + ":" + accessToken).getBytes(StandardCharsets.UTF_8), diff --git a/src/main/java/com/epam/reportportal/saucelabs/GetLogsCommand.java b/src/main/java/com/epam/reportportal/saucelabs/GetLogsCommand.java index 9f1cc1b..378bc42 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/GetLogsCommand.java +++ b/src/main/java/com/epam/reportportal/saucelabs/GetLogsCommand.java @@ -17,53 +17,83 @@ package com.epam.reportportal.saucelabs; import static com.epam.reportportal.saucelabs.SaucelabsExtension.JOB_ID; -import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; import com.epam.reportportal.extension.PluginCommand; -import com.epam.reportportal.rules.commons.validation.Suppliers; -import com.epam.ta.reportportal.entity.integration.Integration; -import com.epam.reportportal.rules.exception.ReportPortalException; import com.epam.reportportal.rules.exception.ErrorType; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.saucelabs.saucerest.SauceREST; -import java.io.IOException; +import com.epam.reportportal.rules.exception.ReportPortalException; +import com.epam.reportportal.saucelabs.client.RestClientBuilder; +import com.epam.reportportal.saucelabs.model.SauceProperties; +import com.epam.ta.reportportal.entity.integration.Integration; +import com.saucelabs.saucerest.model.jobs.JobAssets; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; /** * @author Pavel Bortnik */ +@Slf4j public class GetLogsCommand implements PluginCommand { - private final RestClient restClient; + private final RestClientBuilder restClient; - public GetLogsCommand(RestClient restClient) { + public GetLogsCommand(RestClientBuilder restClient) { this.restClient = restClient; } @Override - public Object executeCommand(Integration system, Map params) { + public Object executeCommand(Integration integration, Map params) { ValidationUtils.validateParams(params); - SauceREST sauce = - restClient.buildSauceClient(system, (String) params.get(DATA_CENTER.getName())); + ValidationUtils.validateIntegrationParams(integration.getParams()); + + SauceProperties sp = new SauceProperties(integration.getParams().getParams()); + sp.setJobId((String) params.get(JOB_ID)); + + return getWebDriverLogs(restClient.buildRestTemplate(sp), sp); + } + + + private Object getWebDriverLogs(RestTemplate restTemplate, SauceProperties sp) { try { - String jobId = (String) params.get(JOB_ID); - String content = - sauce.retrieveResults(sauce.getUsername() + "/jobs/" + jobId + "/assets/log.json"); - if (StringUtils.isEmpty(content)) { - throw new ReportPortalException( - ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - Suppliers.formattedSupplier("Job '{}' not found.", jobId) - ); + JobAssets jobAssets = + restTemplate.getForObject(getJobAssetsUrl(sp), JobAssets.class); + String url = getJobAssetsUrl(sp) + "/" + jobAssets.sauceLog; + return restTemplate.getForObject(url, Object.class); + + } catch (HttpClientErrorException httpException) { + + if (httpException.getStatusCode().is4xxClientError()) { + return getRealDeviceLogs(restTemplate, sp); + + } else { + throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + StringUtils.normalizeSpace("Failed to retrieve job assets")); } - return new ObjectMapper().readValue(content, Object.class); - } catch (IOException e) { - throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, e.getMessage()); } } + + // TODO: handle RD endpoint in a separate plugin command. UI updates required + private Object getRealDeviceLogs(RestTemplate restTemplate, SauceProperties sp) { + String url = "/v1/rdc/jobs/" + sp.getJobId() + "/deviceLogs"; + return restTemplate.getForObject(url, Object.class); + } + @Override public String getName() { return "logs"; } + + private String getJobAssetsUrl(SauceProperties sp) { + return new StringBuilder() + .append("/rest/v1/") + .append(sp.getUsername()) + .append("/jobs/") + .append(sp.getJobId()) + .append("/assets") + .toString(); + } + } diff --git a/src/main/java/com/epam/reportportal/saucelabs/JobInfoCommand.java b/src/main/java/com/epam/reportportal/saucelabs/JobInfoCommand.java index 24b6d5f..0175e8a 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/JobInfoCommand.java +++ b/src/main/java/com/epam/reportportal/saucelabs/JobInfoCommand.java @@ -18,47 +18,68 @@ import static com.epam.reportportal.saucelabs.SaucelabsExtension.JOB_ID; import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; +import static com.epam.reportportal.saucelabs.utils.OldDatacenterResolver.resolveDatacenterDeprecatedName; import com.epam.reportportal.extension.PluginCommand; -import com.epam.reportportal.rules.commons.validation.Suppliers; -import com.epam.ta.reportportal.entity.integration.Integration; -import com.epam.reportportal.rules.exception.ReportPortalException; import com.epam.reportportal.rules.exception.ErrorType; +import com.epam.reportportal.rules.exception.ReportPortalException; +import com.epam.reportportal.saucelabs.utils.JsonUtils; +import com.epam.ta.reportportal.entity.integration.Integration; import com.fasterxml.jackson.databind.ObjectMapper; +import com.saucelabs.saucerest.SauceException; import com.saucelabs.saucerest.SauceREST; +import com.saucelabs.saucerest.model.jobs.Job; +import com.saucelabs.saucerest.model.realdevices.DeviceJob; import java.io.IOException; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author Pavel Bortnik */ +@Slf4j public class JobInfoCommand implements PluginCommand { - private final RestClient restClient; + private final SauceRestClient sauceRestClient; - public JobInfoCommand(RestClient restClient) { - this.restClient = restClient; + public JobInfoCommand(SauceRestClient sauceRestClient) { + this.sauceRestClient = sauceRestClient; } @Override public Object executeCommand(Integration integration, Map params) { ValidationUtils.validateParams(params); + String datacenter = (String) params.get(DATA_CENTER.getName()); + SauceREST sauce = - restClient.buildSauceClient(integration, (String) params.get(DATA_CENTER.getName())); + sauceRestClient.buildSauceClient(integration, resolveDatacenterDeprecatedName(datacenter)); + String jobId = (String) params.get(JOB_ID); + try { - String jobId = (String) params.get(JOB_ID); - String jobInfo = sauce.getJobInfo(jobId); - if (StringUtils.isEmpty(jobInfo)) { - throw new ReportPortalException( - ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - Suppliers.formattedSupplier("Job '{}' not found.", jobId) - ); - } - return new ObjectMapper().readValue(jobInfo, Object.class); + return findJobById(sauce, jobId); } catch (IOException e) { - throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, e.getMessage()); + throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + StringUtils.normalizeSpace(e.getMessage())); + } + } + + private Object findJobById(SauceREST sauce, String jobId) + throws IOException { + Object response; + try { + // find job if exists + Job jobInfo = sauce.getJobsEndpoint().getJobDetails(jobId); + response = new ObjectMapper().readValue(jobInfo.toJson(), Object.class); + } catch (SauceException jobException) { + // If job not exists find real device job + // TODO: introduce separate plugin command. UI updates required + DeviceJob dj = sauce.getRealDevicesEndpoint().getSpecificDeviceJob(jobId); + JsonUtils.toJson(dj, DeviceJob.class); + response = new ObjectMapper() + .readValue(JsonUtils.toJson(dj, DeviceJob.class), Object.class); } + return response; } @Override diff --git a/src/main/java/com/epam/reportportal/saucelabs/RestClient.java b/src/main/java/com/epam/reportportal/saucelabs/SauceRestClient.java similarity index 56% rename from src/main/java/com/epam/reportportal/saucelabs/RestClient.java rename to src/main/java/com/epam/reportportal/saucelabs/SauceRestClient.java index abd78ce..f9784c7 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/RestClient.java +++ b/src/main/java/com/epam/reportportal/saucelabs/SauceRestClient.java @@ -17,53 +17,54 @@ package com.epam.reportportal.saucelabs; import static com.epam.reportportal.saucelabs.SaucelabsProperties.ACCESS_TOKEN; -import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; import static com.epam.reportportal.saucelabs.SaucelabsProperties.USERNAME; import static java.util.Optional.ofNullable; +import com.epam.reportportal.rules.exception.ErrorType; +import com.epam.reportportal.rules.exception.ReportPortalException; import com.epam.ta.reportportal.entity.integration.Integration; import com.epam.ta.reportportal.entity.integration.IntegrationParams; -import com.epam.reportportal.rules.exception.ReportPortalException; -import com.epam.reportportal.rules.exception.ErrorType; import com.saucelabs.saucerest.DataCenter; import com.saucelabs.saucerest.SauceREST; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jasypt.util.text.BasicTextEncryptor; /** * @author Pavel Bortnik */ -public class RestClient { +@Slf4j +public class SauceRestClient { private final BasicTextEncryptor textEncryptor; - public RestClient(BasicTextEncryptor textEncryptor) { + public SauceRestClient(BasicTextEncryptor textEncryptor) { this.textEncryptor = textEncryptor; } - public SauceREST buildSauceClient(Integration system, String dataCenter) { - IntegrationParams params = ofNullable(system.getParams()).orElseThrow( - () -> new ReportPortalException( - ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - "Integration params are not specified." - )); + public SauceREST buildSauceClient(Integration system, DataCenter dataCenter) { + IntegrationParams params = ofNullable(system.getParams()) + .orElseThrow( + () -> new ReportPortalException( + ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + "Integration params are not specified." + )); - String username = USERNAME.getParam(params).orElseThrow( - () -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - "Username is not specified." - )); - String accessToken = textEncryptor.decrypt(ACCESS_TOKEN.getParam(params).orElseThrow( - () -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - "Access token is not specified." - ))); - String dc = ofNullable(dataCenter).orElse(DATA_CENTER.getParam(params).orElseThrow( - () -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, - "Data center is not specified." - ))); + String username = Optional.ofNullable(USERNAME.getParam(params)) + .orElseThrow( + () -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + "Username is not specified." + )); + String accessToken = Optional.ofNullable(textEncryptor.decrypt(ACCESS_TOKEN.getParam(params))) + .orElseThrow( + () -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + "Access token is not specified." + )); - SauceREST sauceREST = new SauceREST(username, accessToken, DataCenter.fromString(dc)); + SauceREST sauceREST = new SauceREST(username, accessToken, dataCenter); - if (StringUtils.isEmpty(sauceREST.getUser())) { + if (StringUtils.isEmpty(sauceREST.getUsername())) { throw new ReportPortalException( ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, "Incorrect Username or Access token"); } diff --git a/src/main/java/com/epam/reportportal/saucelabs/SaucelabsExtension.java b/src/main/java/com/epam/reportportal/saucelabs/SaucelabsExtension.java index 73b0628..a47abd2 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/SaucelabsExtension.java +++ b/src/main/java/com/epam/reportportal/saucelabs/SaucelabsExtension.java @@ -19,14 +19,13 @@ import com.epam.reportportal.extension.CommonPluginCommand; import com.epam.reportportal.extension.PluginCommand; import com.epam.reportportal.extension.ReportPortalExtensionPoint; +import com.epam.reportportal.saucelabs.client.RestClientBuilder; import com.google.common.collect.ImmutableMap; -import com.saucelabs.saucerest.DataCenter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.jasypt.util.text.BasicTextEncryptor; import org.pf4j.Extension; import org.springframework.beans.factory.annotation.Autowired; @@ -43,13 +42,15 @@ public class SaucelabsExtension implements ReportPortalExtensionPoint { private final Supplier>> pluginCommandMapping = new MemoizingSupplier<>(this::getCommands); - private final Supplier restClientSupplier; + private final Supplier restClientSupplier; + private final Supplier newRestClientSupplier; @Autowired private BasicTextEncryptor basicEncryptor; public SaucelabsExtension() { - restClientSupplier = new MemoizingSupplier<>(() -> new RestClient(basicEncryptor)); + restClientSupplier = new MemoizingSupplier<>(() -> new SauceRestClient(basicEncryptor)); + newRestClientSupplier = new MemoizingSupplier<>(() -> new RestClientBuilder(basicEncryptor)); } @@ -58,7 +59,9 @@ public SaucelabsExtension() { Map params = new HashMap<>(); params.put(ALLOWED_COMMANDS, new ArrayList<>(pluginCommandMapping.get().keySet())); params.put(DOCUMENTATION_LINK_FIELD, DOCUMENTATION_LINK); - params.put("dataCenters", Arrays.stream(DataCenter.values()).map(Enum::toString).collect(Collectors.toList())); + //params.put("dataCenters", Arrays.stream(DataCenter.values()).map(Enum::toString).collect(Collectors.toList())); + params.put("dataCenters", Arrays.asList("US", "EU")); + return params; } @@ -73,10 +76,11 @@ public PluginCommand getIntegrationCommand(String commandName) { } private Map> getCommands() { - return ImmutableMap.>builder().put("logs", new GetLogsCommand(restClientSupplier.get())) + return ImmutableMap.>builder() + .put("logs", new GetLogsCommand(newRestClientSupplier.get())) .put("jobInfo", new JobInfoCommand(restClientSupplier.get())) .put("testConnection", new TestConnectionCommand(restClientSupplier.get())) - .put("assets", new AssetsCommand(restClientSupplier.get())) + .put("assets", new AssetsCommand(newRestClientSupplier.get())) .put("token", new GenerateAuthTokenCommand(basicEncryptor)) .build(); diff --git a/src/main/java/com/epam/reportportal/saucelabs/SaucelabsProperties.java b/src/main/java/com/epam/reportportal/saucelabs/SaucelabsProperties.java index f2ba617..41768b2 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/SaucelabsProperties.java +++ b/src/main/java/com/epam/reportportal/saucelabs/SaucelabsProperties.java @@ -18,29 +18,27 @@ import com.epam.ta.reportportal.entity.integration.IntegrationParams; -import java.util.Optional; - /** * @author Pavel Bortnik */ public enum SaucelabsProperties { - USERNAME("username"), - ACCESS_TOKEN("accessToken"), - DATA_CENTER("dataCenter"); + USERNAME("username"), + ACCESS_TOKEN("accessToken"), + DATA_CENTER("dataCenter"); - private final String name; + private final String name; - SaucelabsProperties(String name) { - this.name = name; - } + SaucelabsProperties(String name) { + this.name = name; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public Optional getParam(IntegrationParams params) { - return Optional.ofNullable(params.getParams().get(this.name)).map(String::valueOf); - } + public String getParam(IntegrationParams params) { + return params.getParams().get(this.name).toString(); + } } diff --git a/src/main/java/com/epam/reportportal/saucelabs/TestConnectionCommand.java b/src/main/java/com/epam/reportportal/saucelabs/TestConnectionCommand.java index 652376d..9c2674b 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/TestConnectionCommand.java +++ b/src/main/java/com/epam/reportportal/saucelabs/TestConnectionCommand.java @@ -16,34 +16,37 @@ package com.epam.reportportal.saucelabs; +import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; +import static com.epam.reportportal.saucelabs.utils.OldDatacenterResolver.resolveDatacenterDeprecatedName; + import com.epam.ta.reportportal.entity.integration.Integration; import com.saucelabs.saucerest.SauceREST; -import org.apache.commons.lang3.StringUtils; - import java.util.Map; - -import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; +import org.apache.commons.lang3.StringUtils; /** * @author Pavel Bortnik */ -public class TestConnectionCommand implements com.epam.reportportal.extension.PluginCommand { - - private final RestClient restClient; - - public TestConnectionCommand(RestClient restClient) { - this.restClient = restClient; - } - - @Override - public Boolean executeCommand(Integration integration, Map params) { - SauceREST sauce = restClient.buildSauceClient(integration, (String) params.get(DATA_CENTER.getName())); - String username = sauce.getUser(); - return StringUtils.isNotEmpty(username); - } - - @Override - public String getName() { - return "testConnection"; - } +public class TestConnectionCommand implements + com.epam.reportportal.extension.PluginCommand { + + private final SauceRestClient sauceRestClient; + + public TestConnectionCommand(SauceRestClient sauceRestClient) { + this.sauceRestClient = sauceRestClient; + } + + @Override + public Boolean executeCommand(Integration integration, Map params) { + ValidationUtils.validateIntegrationParams(integration.getParams()); + String datacenter = (String) params.get(DATA_CENTER.getName()); + SauceREST sauce = sauceRestClient.buildSauceClient(integration, + resolveDatacenterDeprecatedName(datacenter)); + return StringUtils.isNotEmpty(sauce.getUsername()); + } + + @Override + public String getName() { + return "testConnection"; + } } diff --git a/src/main/java/com/epam/reportportal/saucelabs/ValidationUtils.java b/src/main/java/com/epam/reportportal/saucelabs/ValidationUtils.java index c803936..278e834 100644 --- a/src/main/java/com/epam/reportportal/saucelabs/ValidationUtils.java +++ b/src/main/java/com/epam/reportportal/saucelabs/ValidationUtils.java @@ -18,9 +18,13 @@ import static com.epam.reportportal.rules.commons.validation.BusinessRule.expect; import static com.epam.reportportal.saucelabs.SaucelabsExtension.JOB_ID; +import static com.epam.reportportal.saucelabs.SaucelabsProperties.ACCESS_TOKEN; +import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; +import static com.epam.reportportal.saucelabs.SaucelabsProperties.USERNAME; -import com.epam.ta.reportportal.commons.Predicates; import com.epam.reportportal.rules.exception.ErrorType; +import com.epam.ta.reportportal.commons.Predicates; +import com.epam.ta.reportportal.entity.integration.IntegrationParams; import java.util.Map; /** @@ -28,9 +32,26 @@ */ public class ValidationUtils { + public static final String IS_NOT_SPECIFIED = " is not specified."; + public static void validateParams(Map params) { expect(params.get(JOB_ID), Predicates.notNull()).verify( ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, JOB_ID + " parameter should be provided"); } + public static void validateIntegrationParams(IntegrationParams integrationParams) { + + expect(integrationParams.getParams(), Predicates.notNull()).verify( + ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, "Integration parameters shouldn't be empty"); + + Map params = integrationParams.getParams(); + expect(params.get(USERNAME.getName()), Predicates.notNull()).verify( + ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, USERNAME + IS_NOT_SPECIFIED); + + expect(params.get(ACCESS_TOKEN.getName()), Predicates.notNull()).verify( + ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, ACCESS_TOKEN + IS_NOT_SPECIFIED); + + expect(params.get(DATA_CENTER.getName()), Predicates.notNull()).verify( + ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, DATA_CENTER + IS_NOT_SPECIFIED); + } } diff --git a/src/main/java/com/epam/reportportal/saucelabs/client/RestClientBuilder.java b/src/main/java/com/epam/reportportal/saucelabs/client/RestClientBuilder.java new file mode 100644 index 0000000..d5b73b3 --- /dev/null +++ b/src/main/java/com/epam/reportportal/saucelabs/client/RestClientBuilder.java @@ -0,0 +1,25 @@ +package com.epam.reportportal.saucelabs.client; + +import com.epam.reportportal.saucelabs.model.SauceProperties; +import lombok.Getter; +import org.jasypt.util.text.BasicTextEncryptor; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.web.client.RestTemplate; + +public class RestClientBuilder { + + private final BasicTextEncryptor textEncryptor; + + public RestClientBuilder(BasicTextEncryptor textEncryptor) { + this.textEncryptor = textEncryptor; + } + + public RestTemplate buildRestTemplate(SauceProperties sp) { + + return new RestTemplateBuilder() + .basicAuthentication(sp.getUsername(), textEncryptor.decrypt(sp.getToken())) + .rootUri(sp.getDatacenter().apiServer) + .build(); + + } +} diff --git a/src/main/java/com/epam/reportportal/saucelabs/model/SauceProperties.java b/src/main/java/com/epam/reportportal/saucelabs/model/SauceProperties.java new file mode 100644 index 0000000..810905f --- /dev/null +++ b/src/main/java/com/epam/reportportal/saucelabs/model/SauceProperties.java @@ -0,0 +1,36 @@ +package com.epam.reportportal.saucelabs.model; + +import static com.epam.reportportal.saucelabs.SaucelabsProperties.ACCESS_TOKEN; +import static com.epam.reportportal.saucelabs.SaucelabsProperties.DATA_CENTER; +import static com.epam.reportportal.saucelabs.SaucelabsProperties.USERNAME; +import static com.epam.reportportal.saucelabs.utils.OldDatacenterResolver.resolveDatacenterDeprecatedName; + +import com.saucelabs.saucerest.DataCenter; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@AllArgsConstructor +@Getter +@Setter +public class SauceProperties { + + private final String username; + private final String token; + private final DataCenter datacenter; + + private String jobId; + + public SauceProperties(String username, String token, DataCenter datacenter) { + this.username = username; + this.token = token; + this.datacenter = datacenter; + } + + public SauceProperties(Map params) { + this.username = (String) params.get(USERNAME.getName()); + this.token = (String) params.get(ACCESS_TOKEN.getName()); + this.datacenter = resolveDatacenterDeprecatedName((String) params.get(DATA_CENTER.getName())); + } +} diff --git a/src/main/java/com/epam/reportportal/saucelabs/utils/JsonUtils.java b/src/main/java/com/epam/reportportal/saucelabs/utils/JsonUtils.java new file mode 100644 index 0000000..ef24956 --- /dev/null +++ b/src/main/java/com/epam/reportportal/saucelabs/utils/JsonUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.epam.reportportal.saucelabs.utils; + +import com.saucelabs.saucerest.MoshiSingleton; +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + +/** + * Utility class for JSON operations using Moshi. + */ +public class JsonUtils { + + private JsonUtils() { + } + + /** + * Converts an object to its JSON representation. + * + * @param the type of the object + * @param object the object to convert to JSON + * @param clazz the class of the object + * @return the JSON representation of the object + */ + public static String toJson(T object, Class clazz) { + Moshi moshi = MoshiSingleton.getInstance(); + JsonAdapter jsonAdapter = (JsonAdapter) moshi.adapter(clazz).nonNull(); + return jsonAdapter.toJson(object); + } +} diff --git a/src/main/java/com/epam/reportportal/saucelabs/utils/OldDatacenterResolver.java b/src/main/java/com/epam/reportportal/saucelabs/utils/OldDatacenterResolver.java new file mode 100644 index 0000000..861d410 --- /dev/null +++ b/src/main/java/com/epam/reportportal/saucelabs/utils/OldDatacenterResolver.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.epam.reportportal.saucelabs.utils; + +import com.epam.reportportal.rules.exception.ErrorType; +import com.epam.reportportal.rules.exception.ReportPortalException; +import com.saucelabs.saucerest.DataCenter; + +/** + * Utility class for resolving deprecated datacenter names to their current equivalents. This is the + * temporary workaround in order to mitigate inconsistency between saved integrations and saucelabs + * rest client {@link DataCenter} Enum. + */ +// TODO: migrate integration old values and remove this class +public class OldDatacenterResolver { + + private OldDatacenterResolver() { + } + + /** + * Resolves the deprecated datacenter name to the current equivalent. + * + * @param oldDatacenter the deprecated datacenter name + * @return the current datacenter name + * @throws ReportPortalException if the datacenter name is invalid + */ + public static DataCenter resolveDatacenterDeprecatedName(String oldDatacenter) { + switch (oldDatacenter) { + case "EU": + case "EU_CENTRAL": + return DataCenter.EU_CENTRAL; + case "US": + case "US_WEST": + return DataCenter.US_WEST; + default: + throw new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, + "Invalid SauceLabs Datacenter value"); + } + + } +}