diff --git a/integrations-dto/src/main/java/com/hp/octane/integrations/dto/entities/EntityConstants.java b/integrations-dto/src/main/java/com/hp/octane/integrations/dto/entities/EntityConstants.java index 40d67919..393f5977 100644 --- a/integrations-dto/src/main/java/com/hp/octane/integrations/dto/entities/EntityConstants.java +++ b/integrations-dto/src/main/java/com/hp/octane/integrations/dto/entities/EntityConstants.java @@ -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 { diff --git a/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunConvertersFactory.java b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunConvertersFactory.java index 295f5a03..915d41ea 100644 --- a/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunConvertersFactory.java +++ b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunConvertersFactory.java @@ -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: diff --git a/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunFramework.java b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunFramework.java index 697b4eaf..e0c8ad33 100644 --- a/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunFramework.java +++ b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/TestsToRunFramework.java @@ -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", ""); diff --git a/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/converters/MfMIAgentConverter.java b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/converters/MfMIAgentConverter.java new file mode 100644 index 00000000..6fc1230e --- /dev/null +++ b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/converters/MfMIAgentConverter.java @@ -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 data, String executionDirectory, Map 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 tests, Map 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 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 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 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 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 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 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; + } + + +} diff --git a/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/MfMIAgentConverterEnrichmentTest.java b/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/MfMIAgentConverterEnrichmentTest.java new file mode 100644 index 00000000..d677626a --- /dev/null +++ b/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/MfMIAgentConverterEnrichmentTest.java @@ -0,0 +1,271 @@ +/* + * Copyright 2017-2026 Open Text + * + * 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 + */ +package com.hp.octane.integrations.executor; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hp.octane.integrations.OctaneClient; +import com.hp.octane.integrations.OctaneConfiguration; +import com.hp.octane.integrations.OctaneSDK; +import com.hp.octane.integrations.dto.connectivity.OctaneResponse; +import com.hp.octane.integrations.executor.converters.MfMIAgentConverter; +import com.hp.octane.integrations.services.configuration.ConfigurationService; +import com.hp.octane.integrations.services.rest.OctaneRestClient; +import com.hp.octane.integrations.services.rest.RestService; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.*; + +import static org.easymock.EasyMock.*; + +public class MfMIAgentConverterEnrichmentTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String RUNS_RESPONSE = "{\n" + + " \"data\": [\n" + + " {\n" + + " \"type\": \"run\",\n" + + " \"workspace_id\": 2001,\n" + + " \"name\": \"tsMIAgent\",\n" + + " \"test_name\": \"Login flow\",\n" + + " \"order_in_suite_run\": 1,\n" + + " \"duration\": null,\n" + + " \"id\": \"1042\",\n" + + " \"subtype\": \"run_manual\",\n" + + " \"au_tester_configuration\": {\n" + + " \"browser\": {\"BROWSER_NAME\": \"Google Chrome\", \"BROWSER_LOCALE\": \"en-US\"},\n" + + " \"agent\": {\"MAX_FAILURES\": 3, \"MAX_NUMBER_OF_STEPS\": 100, \"BROWSER_USE_RUN_TIMEOUT\": 2700}\n" + + " },\n" + + " \"has_attachments\": false,\n" + + " \"parent_suite\": {\"type\": \"run_suite\", \"id\": \"5001\", \"name\": \"tsMIAgent\"},\n" + + " \"run_steps\": {\n" + + " \"total_count\": 1,\n" + + " \"data\": [\n" + + " {\n" + + " \"type\": \"run_step\",\n" + + " \"id\": \"s1\",\n" + + " \"result\": null,\n" + + " \"attachments\": {\"total_count\": 0, \"data\": []},\n" + + " \"index_in_report\": \"1\",\n" + + " \"index_in_script\": 0,\n" + + " \"description\": \"Open login page\",\n" + + " \"actual\": null,\n" + + " \"activity_level\": 0,\n" + + " \"from_call_to_test\": false,\n" + + " \"step_type\": {\"type\": \"list_node\", \"id\": \"list_node.manual_test_run_step_type.normal\", \"name\": \"Normal\"},\n" + + " \"run\": {\"type\": \"run_manual\", \"id\": \"1042\", \"name\": \"tsMIAgent\", \"activity_level\": 0}\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"test\": {\"type\": \"test_manual\", \"id\": \"9001\", \"name\": \"Login flow\", \"subtype\": \"test_manual\", \"activity_level\": 0},\n" + + " \"native_status\": {\"type\": \"list_node\", \"id\": \"list_node.run_native_status.not_completed\", \"name\": \"In Progress\"},\n" + + " \"run_by\": {\"type\": \"workspace_user\", \"id\": \"1001\", \"workspace_id\": 2001, \"full_name\": \"sa@nga\", \"activity_level\": 0}\n" + + " },\n" + + " {\n" + + " \"type\": \"run\",\n" + + " \"workspace_id\": 2001,\n" + + " \"name\": \"tsMIAgent\",\n" + + " \"test_name\": \"Checkout flow\",\n" + + " \"order_in_suite_run\": 2,\n" + + " \"duration\": null,\n" + + " \"id\": \"1043\",\n" + + " \"subtype\": \"run_manual\",\n" + + " \"has_attachments\": false,\n" + + " \"parent_suite\": {\"type\": \"run_suite\", \"id\": \"5001\", \"name\": \"tsMIAgent\"},\n" + + " \"run_steps\": {\n" + + " \"total_count\": 2,\n" + + " \"data\": [\n" + + " {\n" + + " \"type\": \"run_step\",\n" + + " \"id\": \"s2\",\n" + + " \"result\": null,\n" + + " \"attachments\": {\"total_count\": 0, \"data\": []},\n" + + " \"index_in_report\": \"1\",\n" + + " \"index_in_script\": 0,\n" + + " \"description\": \"Add item\",\n" + + " \"actual\": null,\n" + + " \"activity_level\": 0,\n" + + " \"from_call_to_test\": false,\n" + + " \"step_type\": {\"type\": \"list_node\", \"id\": \"list_node.manual_test_run_step_type.normal\", \"name\": \"Normal\"},\n" + + " \"run\": {\"type\": \"run_manual\", \"id\": \"1043\", \"name\": \"tsMIAgent\", \"activity_level\": 0}\n" + + " },\n" + + " {\n" + + " \"type\": \"run_step\",\n" + + " \"id\": \"s3\",\n" + + " \"result\": null,\n" + + " \"attachments\": {\"total_count\": 0, \"data\": []},\n" + + " \"index_in_report\": \"2\",\n" + + " \"index_in_script\": 1,\n" + + " \"description\": \"Verify total\",\n" + + " \"actual\": null,\n" + + " \"activity_level\": 0,\n" + + " \"from_call_to_test\": false,\n" + + " \"step_type\": {\"type\": \"list_node\", \"id\": \"list_node.manual_test_run_step_type.validate\", \"name\": \"Validate\"},\n" + + " \"run\": {\"type\": \"run_manual\", \"id\": \"1043\", \"name\": \"tsMIAgent\", \"activity_level\": 0}\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"test\": {\"type\": \"test_manual\", \"id\": \"9002\", \"name\": \"Checkout flow\", \"subtype\": \"test_manual\", \"activity_level\": 0},\n" + + " \"native_status\": {\"type\": \"list_node\", \"id\": \"list_node.run_native_status.not_completed\", \"name\": \"In Progress\"},\n" + + " \"run_by\": {\"type\": \"workspace_user\", \"id\": \"1001\", \"workspace_id\": 2001, \"full_name\": \"sa@nga\", \"activity_level\": 0}\n" + + " }\n" + + " ],\n" + + " \"total_count\": 2\n" + + "}"; + + private static final String METADATA_WITH_AU = "{\"data\":[{\"name\":\"au_tester_configuration\",\"entity_name\":\"run\"}]}"; + private static final String METADATA_WITHOUT_AU = "{\"data\":[]}"; + + private OctaneClient mockClient; + private OctaneRestClient mockRestClient; + private RestService mockRestService; + private ConfigurationService mockConfigService; + private OctaneConfiguration realConfig; + private Map sdkClientsMap; + + @Before + public void setUp() throws Exception { + mockClient = createMock(OctaneClient.class); + mockRestClient = createMock(OctaneRestClient.class); + mockRestService = createMock(RestService.class); + mockConfigService = createMock(ConfigurationService.class); + + // get a reference to OctaneSDK's internal client registry so we can add/remove our mock + Field f = OctaneSDK.class.getDeclaredField("clients"); + f.setAccessible(true); + sdkClientsMap = (Map) f.get(null); + } + + @After + public void tearDown() { + if (realConfig != null) { + sdkClientsMap.remove(realConfig); + } + } + + @Test + public void enrichTestsData_happyPath() throws Exception { + TestToRunData test1 = new TestToRunData().setTestName("Login flow").addParameters("runId", "1042"); + TestToRunData test2 = new TestToRunData().setTestName("Checkout flow").addParameters("runId", "1043"); + List tests = Arrays.asList(test1, test2); + + // mock Octane REST: first call = metadata check, second call = fetch runs + realConfig = OctaneConfiguration.create("test-config", "https://octane.example.com", "1000"); + expect(mockClient.getConfigurationService()).andReturn(mockConfigService).anyTimes(); + expect(mockClient.getRestService()).andReturn(mockRestService).anyTimes(); + expect(mockConfigService.getConfiguration()).andReturn(realConfig).anyTimes(); + expect(mockRestService.obtainOctaneRestClient()).andReturn(mockRestClient).anyTimes(); + expect(mockRestClient.execute(anyObject())).andReturn(mockResponse(200, METADATA_WITH_AU)); + expect(mockRestClient.execute(anyObject())).andReturn(mockResponse(200, RUNS_RESPONSE)); + replay(mockClient, mockRestClient, mockRestService, mockConfigService); + sdkClientsMap.put(realConfig, mockClient); + + // enrichTestsData calls Octane, fetches manual runs, attaches each run's JSON to its test + MfMIAgentConverter converter = new MfMIAgentConverter(); + converter.enrichTestsData(tests, globalParams()); + + JsonNode run1 = MAPPER.readTree(test1.getParameter("manualRunData")); + Assert.assertEquals("1042", run1.path("id").asText()); + Assert.assertEquals("tsMIAgent", run1.path("name").asText()); + Assert.assertEquals("Login flow", run1.path("test_name").asText()); + Assert.assertEquals(1, run1.path("order_in_suite_run").asInt()); + Assert.assertEquals("run_manual", run1.path("subtype").asText()); + Assert.assertFalse(run1.path("has_attachments").asBoolean(true)); + Assert.assertEquals("5001", run1.path("parent_suite").path("id").asText()); + Assert.assertEquals("Google Chrome", run1.path("au_tester_configuration").path("browser").path("BROWSER_NAME").asText()); + Assert.assertEquals(3, run1.path("au_tester_configuration").path("agent").path("MAX_FAILURES").asInt()); + Assert.assertEquals("In Progress", run1.path("native_status").path("name").asText()); + Assert.assertEquals("sa@nga", run1.path("run_by").path("full_name").asText()); + Assert.assertTrue(run1.has("run_steps")); + Assert.assertEquals("run_step", run1.path("run_steps").path("data").get(0).path("type").asText()); + Assert.assertEquals("Normal", run1.path("run_steps").path("data").get(0).path("step_type").path("name").asText()); + Assert.assertEquals("test_manual", run1.path("test").path("subtype").asText()); + + JsonNode run2 = MAPPER.readTree(test2.getParameter("manualRunData")); + Assert.assertEquals("1043", run2.path("id").asText()); + Assert.assertEquals("Checkout flow", run2.path("test_name").asText()); + Assert.assertEquals("5001", run2.path("parent_suite").path("id").asText()); + Assert.assertEquals(2, run2.path("run_steps").path("data").size()); + Assert.assertEquals("Validate", run2.path("run_steps").path("data").get(1).path("step_type").path("name").asText()); + } + + @Test + public void enrichTestsData_fallbackWhenNoAuConfig() throws Exception { + TestToRunData test = new TestToRunData().setTestName("T1").addParameters("runId", "1042"); + String legacyRuns = "{\"data\":[{\"type\":\"run\",\"workspace_id\":2001,\"name\":\"tsMIAgent\",\"id\":\"1042\",\"test_name\":\"T1\",\"subtype\":\"run_manual\",\"order_in_suite_run\":1,\"duration\":null,\"has_attachments\":false,\"parent_suite\":{\"type\":\"run_suite\",\"id\":\"5001\",\"name\":\"tsMIAgent\"},\"run_steps\":{\"total_count\":0,\"data\":[]},\"test\":{\"type\":\"test_manual\",\"id\":\"9101\",\"name\":\"T1\",\"subtype\":\"test_manual\",\"activity_level\":0},\"native_status\":{\"type\":\"list_node\",\"id\":\"list_node.run_native_status.not_completed\",\"name\":\"In Progress\"},\"run_by\":{\"type\":\"workspace_user\",\"id\":\"1001\",\"workspace_id\":2001,\"full_name\":\"sa@nga\",\"activity_level\":0}}],\"total_count\":1}"; + + // mock Octane REST: metadata says no au_tester_configuration, then fetch runs with legacy URL + realConfig = OctaneConfiguration.create("test-config", "https://octane.example.com", "1000"); + expect(mockClient.getConfigurationService()).andReturn(mockConfigService).anyTimes(); + expect(mockClient.getRestService()).andReturn(mockRestService).anyTimes(); + expect(mockConfigService.getConfiguration()).andReturn(realConfig).anyTimes(); + expect(mockRestService.obtainOctaneRestClient()).andReturn(mockRestClient).anyTimes(); + expect(mockRestClient.execute(anyObject())).andReturn(mockResponse(200, METADATA_WITHOUT_AU)); + expect(mockRestClient.execute(anyObject())).andReturn(mockResponse(200, legacyRuns)); + replay(mockClient, mockRestClient, mockRestService, mockConfigService); + sdkClientsMap.put(realConfig, mockClient); + + MfMIAgentConverter converter = new MfMIAgentConverter(); + converter.enrichTestsData(Collections.singletonList(test), globalParams()); + + JsonNode node = MAPPER.readTree(test.getParameter("manualRunData")); + Assert.assertEquals("1042", node.path("id").asText()); + Assert.assertEquals("tsMIAgent", node.path("name").asText()); + Assert.assertEquals("run_manual", node.path("subtype").asText()); + Assert.assertEquals("5001", node.path("parent_suite").path("id").asText()); + Assert.assertEquals("sa@nga", node.path("run_by").path("full_name").asText()); + Assert.assertFalse(node.has("au_tester_configuration")); + } + + @Test + public void enrichTestsData_missingRunId_throws() throws Exception { + TestToRunData test = new TestToRunData().setTestName("Bad test"); // no runId + + realConfig = OctaneConfiguration.create("test-config", "https://octane.example.com", "1000"); + expect(mockClient.getConfigurationService()).andReturn(mockConfigService).anyTimes(); + expect(mockClient.getRestService()).andReturn(mockRestService).anyTimes(); + expect(mockConfigService.getConfiguration()).andReturn(realConfig).anyTimes(); + expect(mockRestService.obtainOctaneRestClient()).andReturn(mockRestClient).anyTimes(); + expect(mockRestClient.execute(anyObject())).andReturn(mockResponse(200, METADATA_WITH_AU)); + expect(mockRestClient.execute(anyObject())).andReturn(mockResponse(200, RUNS_RESPONSE)); + replay(mockClient, mockRestClient, mockRestService, mockConfigService); + sdkClientsMap.put(realConfig, mockClient); + + IllegalArgumentException ex = Assert.assertThrows( + IllegalArgumentException.class, + () -> new MfMIAgentConverter().enrichTestsData(Collections.singletonList(test), globalParams()) + ); + + Assert.assertEquals("Missing runId parameter for MI Agent test 'Bad test'", ex.getMessage()); + } + + // --- Helpers --- + + private Map globalParams() { + Map params = new HashMap<>(); + params.put("octaneConfigId", "test-config"); + params.put("octaneWorkspaceId", "1001"); + params.put("suiteRunId", "5001"); + return params; + } + + private OctaneResponse mockResponse(int status, String body) { + OctaneResponse r = createMock(OctaneResponse.class); + expect(r.getStatus()).andReturn(status).anyTimes(); + expect(r.getBody()).andReturn(body).anyTimes(); + replay(r); + return r; + } +} diff --git a/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/TestsToRunConverterTest.java b/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/TestsToRunConverterTest.java index ddecc611..4ba679a9 100644 --- a/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/TestsToRunConverterTest.java +++ b/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/TestsToRunConverterTest.java @@ -31,10 +31,14 @@ */ package com.hp.octane.integrations.executor; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; import org.junit.Test; +import java.util.Arrays; + import static com.hp.octane.integrations.executor.TestsToRunFramework.JUnit4; +import static com.hp.octane.integrations.executor.TestsToRunFramework.MF_MI_AGENT; import static com.hp.octane.integrations.executor.TestsToRunFramework.MF_UFT; public class TestsToRunConverterTest { @@ -71,6 +75,12 @@ public class TestsToRunConverterTest { "\r\n " + "\r\n\r\n"; + private final static String MI_AGENT_RUN_1042 = "{\"type\":\"run\",\"workspace_id\":2001,\"name\":\"tsMIAgent\",\"test_name\":\"Login flow\",\"order_in_suite_run\":1,\"duration\":null,\"id\":\"1042\",\"subtype\":\"run_manual\",\"au_tester_configuration\":{\"browser\":{\"BROWSER_NAME\":\"Google Chrome\",\"BROWSER_LOCALE\":\"en-US\"},\"agent\":{\"MAX_FAILURES\":3,\"MAX_NUMBER_OF_STEPS\":100,\"BROWSER_USE_RUN_TIMEOUT\":2700}},\"has_attachments\":false,\"parent_suite\":{\"type\":\"run_suite\",\"id\":\"5001\",\"name\":\"tsMIAgent\"},\"run_steps\":{\"total_count\":1,\"data\":[{\"type\":\"run_step\",\"id\":\"s1\",\"result\":null,\"attachments\":{\"total_count\":0,\"data\":[]},\"index_in_report\":\"1\",\"index_in_script\":0,\"description\":\"Open login page\",\"actual\":null,\"activity_level\":0,\"from_call_to_test\":false,\"step_type\":{\"type\":\"list_node\",\"id\":\"list_node.manual_test_run_step_type.normal\",\"name\":\"Normal\"},\"run\":{\"type\":\"run_manual\",\"id\":\"1042\",\"name\":\"tsMIAgent\",\"activity_level\":0}}]},\"test\":{\"type\":\"test_manual\",\"id\":\"9001\",\"name\":\"Login flow\",\"subtype\":\"test_manual\",\"activity_level\":0},\"native_status\":{\"type\":\"list_node\",\"id\":\"list_node.run_native_status.not_completed\",\"name\":\"In Progress\"},\"run_by\":{\"type\":\"workspace_user\",\"id\":\"1001\",\"workspace_id\":2001,\"activity_level\":0,\"full_name\":\"sa@nga\"}}"; + + private final static String MI_AGENT_RUN_1043 = "{\"type\":\"run\",\"workspace_id\":2001,\"name\":\"tsMIAgent\",\"test_name\":\"Checkout flow\",\"order_in_suite_run\":2,\"duration\":null,\"id\":\"1043\",\"subtype\":\"run_manual\",\"has_attachments\":false,\"parent_suite\":{\"type\":\"run_suite\",\"id\":\"5001\",\"name\":\"tsMIAgent\"},\"run_steps\":{\"total_count\":2,\"data\":[{\"type\":\"run_step\",\"id\":\"s2\",\"result\":null,\"attachments\":{\"total_count\":0,\"data\":[]},\"index_in_report\":\"1\",\"index_in_script\":0,\"description\":\"Add item\",\"actual\":null,\"activity_level\":0,\"from_call_to_test\":false,\"step_type\":{\"type\":\"list_node\",\"id\":\"list_node.manual_test_run_step_type.normal\",\"name\":\"Normal\"},\"run\":{\"type\":\"run_manual\",\"id\":\"1043\",\"name\":\"tsMIAgent\",\"activity_level\":0}},{\"type\":\"run_step\",\"id\":\"s3\",\"result\":null,\"attachments\":{\"total_count\":0,\"data\":[]},\"index_in_report\":\"2\",\"index_in_script\":1,\"description\":\"Verify total\",\"actual\":null,\"activity_level\":0,\"from_call_to_test\":false,\"step_type\":{\"type\":\"list_node\",\"id\":\"list_node.manual_test_run_step_type.validate\",\"name\":\"Validate\"},\"run\":{\"type\":\"run_manual\",\"id\":\"1043\",\"name\":\"tsMIAgent\",\"activity_level\":0}}]},\"test\":{\"type\":\"test_manual\",\"id\":\"9002\",\"name\":\"Checkout flow\",\"subtype\":\"test_manual\",\"activity_level\":0},\"native_status\":{\"type\":\"list_node\",\"id\":\"list_node.run_native_status.not_completed\",\"name\":\"In Progress\"},\"run_by\":{\"type\":\"workspace_user\",\"id\":\"1001\",\"workspace_id\":2001,\"activity_level\":0,\"full_name\":\"sa@nga\"}}"; + + private final static String MI_AGENT_EXPECTED_MANIFEST = "{\"data\":[" + MI_AGENT_RUN_1042 + "," + MI_AGENT_RUN_1043 + "],\"total_count\":2}"; + private String converterTest(TestsToRunFramework framework, String rawData) { TestsToRunConverter converter = TestsToRunConvertersFactory.createConverter(framework); @@ -107,4 +117,29 @@ public void uftConverterStringTest() { Assert.assertEquals(outputUFTResult, actual); } + @Test + public void miAgentConverterManifestTest() throws Exception { + TestToRunData first = new TestToRunData() + .setTestName("Login flow") + .addParameters("runId", "1042") + .addParameters("manualRunData", MI_AGENT_RUN_1042); + TestToRunData second = new TestToRunData() + .setTestName("Checkout flow") + .addParameters("runId", "1043") + .addParameters("manualRunData", MI_AGENT_RUN_1043); + + String actual = TestsToRunConvertersFactory.createConverter(MF_MI_AGENT) + .convert(Arrays.asList(first, second), "", null) + .getConvertedTestsString(); + + ObjectMapper objectMapper = new ObjectMapper(); + Assert.assertEquals(objectMapper.readTree(MI_AGENT_EXPECTED_MANIFEST), objectMapper.readTree(actual)); + + Assert.assertEquals("Google Chrome", objectMapper.readTree(actual) + .path("data").get(0) + .path("au_tester_configuration").path("browser").path("BROWSER_NAME").asText()); + Assert.assertEquals("sa@nga", objectMapper.readTree(actual) + .path("data").get(1) + .path("run_by").path("full_name").asText()); + } }