Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public static class Executors extends Base {
public static final String ENTITY_NAME = "executor";
public static final String TEST_RUNNER_SUBTYPE_ENTITY_NAME = "test_runner";
public static final String UFT_TEST_RUNNER_SUBTYPE_ENTITY_NAME = "uft_test_runner";
public static final String AUTE_TEST_RUNNER_SUBTYPE_ENTITY_NAME = "aute_test_runner";
}

public static class CIServer extends Base {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public static TestsToRunConverter createConverter(TestsToRunFramework framework)
return new ProtractorConverter();
case Gradle:
return new GradleConverter();
case MF_MI_AGENT:
return new MfMIAgentConverter();
case Custom:
return new CustomConverter();
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public enum TestsToRunFramework implements Serializable {
JBehave("jbehave", "JBehave over Maven", JBehaveConverter.FORMAT),
Protractor("protractor", "Protractor", ProtractorConverter.FORMAT),
Gradle("gradle", "Gradle", GradleConverter.FORMAT),
MF_MI_AGENT("mi_agent", "Open Text Autonomous Tester", ""),
Custom("custom", "Custom", "");


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package com.hp.octane.integrations.executor.converters;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hp.octane.integrations.OctaneClient;
import com.hp.octane.integrations.OctaneConfiguration;
import com.hp.octane.integrations.OctaneSDK;
import com.hp.octane.integrations.dto.DTOFactory;
import com.hp.octane.integrations.dto.connectivity.HttpMethod;
import com.hp.octane.integrations.dto.connectivity.OctaneRequest;
import com.hp.octane.integrations.dto.connectivity.OctaneResponse;
import com.hp.octane.integrations.executor.TestToRunData;
import com.hp.octane.integrations.executor.TestsToRunConverter;
import com.hp.octane.integrations.services.rest.OctaneRestClient;
import com.hp.octane.integrations.utils.SdkStringUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.hp.octane.integrations.services.rest.RestService.ACCEPT_HEADER;
import static com.hp.octane.integrations.utils.SdkConstants.JobParameters.*;

public class MfMIAgentConverter extends TestsToRunConverter {

private static final Logger logger = LogManager.getLogger(MfMIAgentConverter.class);

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private static final String RUN_ID_PARAMETER = "runId";

private static final String MANUAL_RUN_DATA_PARAMETER = "manualRunData";

private static final String RUNS_PATH_TEMPLATE = "/api/shared_spaces/%s/workspaces/%s/runs";

private static final String RUNS_FIELDS = "run_steps{actual,description,result,run,step_type,attachments,from_call_to_test,index_in_script,index_in_report},id,has_attachments,order_in_suite_run,test,run_by,name,test_name,duration,subtype,native_status,parent_suite,run_by{full_name},test{subtype}";

private static final String RUNS_FIELDS_WITH_AU_TESTER_CONFIG = "run_steps{actual,description,result,run,step_type,attachments,from_call_to_test,index_in_script,index_in_report},id,has_attachments,order_in_suite_run,test,run_by,name,test_name,duration,subtype,native_status,parent_suite,au_tester_configuration,run_by{full_name},test{subtype}";

private static final String METADATA_PATH_TEMPLATE = "/api/shared_spaces/%s/workspaces/%s/metadata/fields";

@Override
protected String convertInternal(List<TestToRunData> data, String executionDirectory, Map<String, String> globalParameters) {
ObjectNode manifest = OBJECT_MAPPER.createObjectNode();
ArrayNode runs = OBJECT_MAPPER.createArrayNode();

if (data != null) {
for (TestToRunData test : data) {
String manualRunData = test.getParameter(MANUAL_RUN_DATA_PARAMETER);
if (SdkStringUtils.isEmpty(manualRunData)) {
throw new IllegalArgumentException("Missing MI Agent run data for test '" + test.getTestName() + "'");
}

try {
runs.add(OBJECT_MAPPER.readTree(manualRunData));
} catch (IOException e) {
throw new IllegalArgumentException("Invalid MI Agent run data for test '" + test.getTestName() + "'", e);
}
}
}

manifest.set("data", runs);
manifest.put("total_count", runs.size());
try {
return OBJECT_MAPPER.writeValueAsString(manifest);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to serialize MI Agent execution manifest", e);
}
}

@Override
public void enrichTestsData(List<TestToRunData> tests, Map<String, String> globalParameters) {
if (tests == null || tests.isEmpty()) {
return;
}

OctaneClient octaneClient = getOctaneClient(globalParameters);
OctaneConfiguration octaneConfig = octaneClient.getConfigurationService().getConfiguration();
String workspaceId = getRequiredParameter(globalParameters, OCTANE_WORKSPACE_PARAMETER_NAME);
String suiteRunId = getRequiredParameter(globalParameters, SUITE_RUN_ID_PARAMETER_NAME);

String responseBody = fetchManualRuns(octaneClient, octaneConfig, workspaceId, suiteRunId);
Map<String, JsonNode> runsById = indexRunsById(responseBody);

for (TestToRunData test : tests) {
String runId = getRequiredRunId(test);
JsonNode runNode = runsById.get(runId);
if (runNode == null) {
throw new IllegalArgumentException("Failed to find MI Agent manual run '" + runId + "' for test '" + test.getTestName() + "'");
}

try {
test.addParameters(MANUAL_RUN_DATA_PARAMETER, OBJECT_MAPPER.writeValueAsString(runNode));
} catch (IOException e) {
throw new IllegalArgumentException("Failed to store MI Agent run data for test '" + test.getTestName() + "'", e);
}
}
}

private OctaneClient getOctaneClient(Map<String, String> globalParameters) {
String octaneConfigId = getRequiredParameter(globalParameters, OCTANE_CONFIG_ID_PARAMETER_NAME);
return OctaneSDK.getClientByInstanceId(octaneConfigId);
}

private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId, String suiteRunId) {
String query = "\"(parent_suite={id=" + suiteRunId + "};subtype IN 'run_manual')\"";

if (hasAutonomousTesterConfiguration(octaneClient, octaneConfig, workspaceId)) {
String newUrl = buildRunsUrl(octaneConfig, workspaceId, RUNS_FIELDS_WITH_AU_TESTER_CONFIG, query);
OctaneResponse response = executeGet(octaneClient, newUrl);
if (response.getStatus() == HttpStatus.SC_OK && response.getBody() != null) {
return response.getBody();
}
logger.warn("Failed to retrieve MI Agent runs with au_tester_configuration, falling back to legacy runs query. Status: {}", response.getStatus());
}

String oldUrl = buildRunsUrl(octaneConfig, workspaceId, RUNS_FIELDS, query);
OctaneResponse response = executeGet(octaneClient, oldUrl);
if (response.getStatus() == HttpStatus.SC_OK && response.getBody() != null) {
return response.getBody();
}

throw new IllegalArgumentException("Failed to retrieve MI Agent manual runs from Octane. Status: " + response.getStatus());
}

private boolean hasAutonomousTesterConfiguration(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId) {
String metadataUrl = buildMetadataUrl(octaneConfig, workspaceId);
OctaneResponse response = executeGet(octaneClient, metadataUrl);
return response.getStatus() == HttpStatus.SC_OK && response.getBody() != null && response.getBody().contains("au_tester_configuration");
}

private String buildMetadataUrl(OctaneConfiguration cfg, String workspaceId) {
try {
return new URIBuilder(cfg.getUrl() + String.format(METADATA_PATH_TEMPLATE, cfg.getSharedSpace(), workspaceId))
.addParameter("query", "\"entity_name='run';name='au_tester_configuration'\"")
.build()
.toString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Failed to build metadata URL", e);
}
}

private String buildRunsUrl(OctaneConfiguration cfg, String workspaceId, String fields, String query) {
try {
return new URIBuilder(cfg.getUrl() + String.format(RUNS_PATH_TEMPLATE, cfg.getSharedSpace(), workspaceId))
.addParameter("fields", fields)
.addParameter("limit", "30")
.addParameter("offset", "0")
.addParameter("order_by", "order_in_suite_run,id")
.addParameter("query", query)
.build()
.toString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Failed to build runs URL", e);
}
}

private OctaneResponse executeGet(OctaneClient octaneClient, String url) {
Map<String, String> headers = new HashMap<>();
headers.put(ACCEPT_HEADER, ContentType.APPLICATION_JSON.getMimeType());
headers.put(OctaneRestClient.CLIENT_TYPE_HEADER, OctaneRestClient.CLIENT_TYPE_VALUE);

OctaneRequest request = DTOFactory.getInstance()
.newDTO(OctaneRequest.class)
.setMethod(HttpMethod.GET)
.setHeaders(headers)
.setUrl(url);

try {
OctaneResponse response = octaneClient.getRestService().obtainOctaneRestClient().execute(request);
return Objects.requireNonNull(response, "Octane REST client returned null response");
} catch (IOException e) {
throw new IllegalArgumentException("Failed to execute Octane request: " + url, e);
}
}

private Map<String, JsonNode> indexRunsById(String responseBody) {
try {
JsonNode root = OBJECT_MAPPER.readTree(responseBody);
JsonNode runs = root.path("data");
if (!runs.isArray()) {
throw new IllegalArgumentException("Unexpected MI Agent runs response: missing data array");
}

Map<String, JsonNode> runsById = new HashMap<>();
for (JsonNode runNode : runs) {
String runId = runNode.path("id").asText();
if (SdkStringUtils.isNotEmpty(runId)) {
runsById.put(runId, runNode);
}
}
return runsById;
} catch (IOException e) {
throw new IllegalArgumentException("Failed to parse MI Agent runs response", e);
}
}

private String getRequiredRunId(TestToRunData test) {
String runId = test.getParameter(RUN_ID_PARAMETER);
if (SdkStringUtils.isEmpty(runId)) {
throw new IllegalArgumentException("Missing runId parameter for MI Agent test '" + test.getTestName() + "'");
}
return runId;
}

private String getRequiredParameter(Map<String, String> globalParameters, String key) {
if (globalParameters == null) {
throw new IllegalArgumentException("Missing global parameters required for MI Agent enrichment");
}

String value = globalParameters.get(key);
if (SdkStringUtils.isEmpty(value)) {
throw new IllegalArgumentException("Missing global parameter '" + key + "' required for MI Agent enrichment");
}
return value;
}


}
Loading