Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EPMRPP-93787 Update saucelabs client #27

Merged
merged 2 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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'))

}
}

Expand Down
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
version=5.11.0
version=5.11.0
lombokVersion=1.18.34
84 changes: 60 additions & 24 deletions src/main/java/com/epam/reportportal/saucelabs/AssetsCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,92 @@
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 <a href="mailto:[email protected]">Pavel Bortnik</a>
*/
@Slf4j
public class AssetsCommand implements PluginCommand<Object> {

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<String, Object> 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<String, String> 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());
}
}

@Override
public String getName() {
return "assets";
}


public String toJson(JobAssets jobAssets) {
Moshi moshi = MoshiSingleton.getInstance();

JsonAdapter<JobAssets> jsonAdapter = moshi.adapter(JobAssets.class).nonNull();
return jsonAdapter.toJson(jobAssets);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
Expand Down
76 changes: 53 additions & 23 deletions src/main/java/com/epam/reportportal/saucelabs/GetLogsCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="mailto:[email protected]">Pavel Bortnik</a>
*/
@Slf4j
public class GetLogsCommand implements PluginCommand<Object> {

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<String, Object> params) {
public Object executeCommand(Integration integration, Map<String, Object> 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();
}

}
55 changes: 38 additions & 17 deletions src/main/java/com/epam/reportportal/saucelabs/JobInfoCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="mailto:[email protected]">Pavel Bortnik</a>
*/
@Slf4j
public class JobInfoCommand implements PluginCommand<Object> {

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
Expand Down
Loading
Loading