From b7159b9bf9991684605930d480c05e792999cee3 Mon Sep 17 00:00:00 2001 From: dradu Date: Fri, 31 Jul 2026 11:55:43 +0300 Subject: [PATCH 1/5] us #3198016: implement converter --- .../dto/entities/EntityConstants.java | 1 + .../executor/TestsToRunConvertersFactory.java | 2 + .../executor/TestsToRunFramework.java | 1 + .../converters/MfMIAgentConverter.java | 201 ++++++++++++++++++ .../MfMIAgentConverterEnrichmentTest.java | 196 +++++++++++++++++ .../executor/TestsToRunConverterTest.java | 24 +++ 6 files changed, 425 insertions(+) create mode 100644 integrations-sdk/src/main/java/com/hp/octane/integrations/executor/converters/MfMIAgentConverter.java create mode 100644 integrations-sdk/src/test/java/com/hp/octane/integrations/executor/MfMIAgentConverterEnrichmentTest.java 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..14da006c --- /dev/null +++ b/integrations-sdk/src/main/java/com/hp/octane/integrations/executor/converters/MfMIAgentConverter.java @@ -0,0 +1,201 @@ +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.entity.ContentType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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 GET_MANUAL_RUN_STEPS_URL_TEMPLATE = "/api/shared_spaces/%s/workspaces/%s/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}&limit=30&offset=0&order_by=order_in_suite_run,id&query=\"(parent_suite={id=%s};subtype IN 'run_manual')\""; + + private static final String GET_MANUAL_RUN_STEPS_URL_TEMPLATE_WITH_AU_TESTER_CONFIG = "/api/shared_spaces/%s/workspaces/%s/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,au_tester_configuration,run_by{full_name},test{subtype}&limit=30&offset=0&order_by=order_in_suite_run,id&query=\"(parent_suite={id=%s};subtype IN 'run_manual')\""; + + private static final String GET_MANUAL_RUN_METADATA = "/api/shared_spaces/%s/workspaces/%s/metadata/fields?query=\"entity_name='run';name='au_tester_configuration'\""; + + @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 IllegalStateException("Missing MI Agent run data for test '" + test.getTestName() + "'"); + } + + try { + runs.add(OBJECT_MAPPER.readTree(manualRunData)); + } catch (IOException e) { + throw new IllegalStateException("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 IllegalStateException("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 IllegalStateException("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 IllegalStateException("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); + OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(octaneConfigId); + if (octaneClient == null) { + throw new IllegalStateException("Failed to resolve Octane client for config id '" + octaneConfigId + "'"); + } + return octaneClient; + } + + private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId, String suiteRunId) { + String sharedSpaceId = octaneConfig.getSharedSpace(); + String oldUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_STEPS_URL_TEMPLATE, sharedSpaceId, workspaceId, suiteRunId); + String newUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_STEPS_URL_TEMPLATE_WITH_AU_TESTER_CONFIG, sharedSpaceId, workspaceId, suiteRunId); + + if (hasAutonomousTesterConfiguration(octaneClient, octaneConfig, workspaceId)) { + OctaneResponse response = executeGet(octaneClient, newUrl); + if (response != null && response.getStatus() == HttpStatus.SC_OK) { + return response.getBody(); + } + logger.warn("Failed to retrieve MI Agent runs with au_tester_configuration, falling back to legacy runs query. Status: {}", response != null ? response.getStatus() : "(null)"); + } + + OctaneResponse response = executeGet(octaneClient, oldUrl); + if (response != null && response.getStatus() == HttpStatus.SC_OK) { + return response.getBody(); + } + + throw new IllegalStateException("Failed to retrieve MI Agent manual runs from Octane. Status: " + (response != null ? response.getStatus() : "(null)")); + } + + private boolean hasAutonomousTesterConfiguration(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId) { + String metadataUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_METADATA, octaneConfig.getSharedSpace(), workspaceId); + OctaneResponse response = executeGet(octaneClient, metadataUrl); + return response != null && response.getStatus() == HttpStatus.SC_OK && response.getBody() != null && response.getBody().contains("au_tester_configuration"); + } + + 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 { + return octaneClient.getRestService().obtainOctaneRestClient().execute(request); + } catch (IOException e) { + throw new IllegalStateException("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 IllegalStateException("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 IllegalStateException("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 IllegalStateException("Missing runId parameter for MI Agent test '" + test.getTestName() + "'"); + } + return runId; + } + + private String getRequiredParameter(Map globalParameters, String key) { + if (globalParameters == null) { + throw new IllegalStateException("Missing global parameters required for MI Agent enrichment"); + } + + String value = globalParameters.get(key); + if (SdkStringUtils.isEmpty(value)) { + throw new IllegalStateException("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..24ac6089 --- /dev/null +++ b/integrations-sdk/src/test/java/com/hp/octane/integrations/executor/MfMIAgentConverterEnrichmentTest.java @@ -0,0 +1,196 @@ +/* + * 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" + + " \"id\": \"1042\",\n" + + " \"test_name\": \"Login flow\",\n" + + " \"subtype\": \"run_manual\",\n" + + " \"order_in_suite_run\": 1,\n" + + " \"native_status\": {\"type\": \"list_node\", \"id\": \"status_ready\", \"name\": \"Ready\"},\n" + + " \"test\": {\"subtype\": \"test_manual\"},\n" + + " \"run_steps\": {\n" + + " \"total_count\": 1,\n" + + " \"data\": [{\"id\": \"s1\", \"step_type\": {\"name\": \"Normal\"}, \"description\": \"Open login page\"}]\n" + + " },\n" + + " \"au_tester_configuration\": {\"BROWSER_NAME\": \"chrome\"}\n" + + " },\n" + + " {\n" + + " \"type\": \"run\",\n" + + " \"id\": \"1043\",\n" + + " \"test_name\": \"Checkout flow\",\n" + + " \"subtype\": \"run_manual\",\n" + + " \"order_in_suite_run\": 2,\n" + + " \"native_status\": {\"type\": \"list_node\", \"id\": \"status_ready\", \"name\": \"Ready\"},\n" + + " \"test\": {\"subtype\": \"test_manual\"},\n" + + " \"run_steps\": {\n" + + " \"total_count\": 2,\n" + + " \"data\": [\n" + + " {\"id\": \"s2\", \"step_type\": {\"name\": \"Normal\"}, \"description\": \"Add item\"},\n" + + " {\"id\": \"s3\", \"step_type\": {\"name\": \"Validate\"}, \"description\": \"Verify total\"}\n" + + " ]\n" + + " }\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.assertTrue(run1.has("run_steps")); + Assert.assertEquals("chrome", run1.path("au_tester_configuration").path("BROWSER_NAME").asText()); + + JsonNode run2 = MAPPER.readTree(test2.getParameter("manualRunData")); + Assert.assertEquals("1043", run2.path("id").asText()); + Assert.assertEquals(2, run2.path("run_steps").path("data").size()); + } + + @Test + public void enrichTestsData_fallbackWhenNoAuConfig() throws Exception { + TestToRunData test = new TestToRunData().setTestName("T1").addParameters("runId", "1042"); + String legacyRuns = "{\"data\":[{\"type\":\"run\",\"id\":\"1042\",\"test_name\":\"T1\",\"run_steps\":{\"data\":[],\"total_count\":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.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); + + IllegalStateException ex = Assert.assertThrows( + IllegalStateException.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..d5a3dbe9 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 { @@ -107,4 +111,24 @@ 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", "{\"type\":\"run\",\"id\":\"1042\",\"test_name\":\"Login flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":1,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":1,\"data\":[{\"id\":\"s1\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Open login page\"}]},\"au_tester_configuration\":{\"BROWSER_NAME\":\"chrome\"}}"); + TestToRunData second = new TestToRunData() + .setTestName("Checkout flow") + .addParameters("runId", "1043") + .addParameters("manualRunData", "{\"type\":\"run\",\"id\":\"1043\",\"test_name\":\"Checkout flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":2,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":2,\"data\":[{\"id\":\"s2\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Add item\"},{\"id\":\"s3\",\"step_type\":{\"name\":\"Validate\"},\"description\":\"Verify total\"}]}}"); + + String actual = TestsToRunConvertersFactory.createConverter(MF_MI_AGENT) + .convert(Arrays.asList(first, second), "", null) + .getConvertedTestsString(); + + String expected = "{\"data\":[{\"type\":\"run\",\"id\":\"1042\",\"test_name\":\"Login flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":1,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":1,\"data\":[{\"id\":\"s1\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Open login page\"}]},\"au_tester_configuration\":{\"BROWSER_NAME\":\"chrome\"}},{\"type\":\"run\",\"id\":\"1043\",\"test_name\":\"Checkout flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":2,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":2,\"data\":[{\"id\":\"s2\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Add item\"},{\"id\":\"s3\",\"step_type\":{\"name\":\"Validate\"},\"description\":\"Verify total\"}]}}],\"total_count\":2}"; + + ObjectMapper objectMapper = new ObjectMapper(); + Assert.assertEquals(objectMapper.readTree(expected), objectMapper.readTree(actual)); + } } From 07bac25e7202b1335a922a85f5942e38eed17440 Mon Sep 17 00:00:00 2001 From: dradu Date: Fri, 31 Jul 2026 15:46:20 +0300 Subject: [PATCH 2/5] us #3198016: implement converter --- .../converters/MfMIAgentConverter.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) 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 index 14da006c..d6a3b4dd 100644 --- 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 @@ -24,6 +24,7 @@ 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.*; @@ -104,11 +105,7 @@ public void enrichTestsData(List tests, Map globa private OctaneClient getOctaneClient(Map globalParameters) { String octaneConfigId = getRequiredParameter(globalParameters, OCTANE_CONFIG_ID_PARAMETER_NAME); - OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(octaneConfigId); - if (octaneClient == null) { - throw new IllegalStateException("Failed to resolve Octane client for config id '" + octaneConfigId + "'"); - } - return octaneClient; + return OctaneSDK.getClientByInstanceId(octaneConfigId); } private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId, String suiteRunId) { @@ -118,24 +115,24 @@ private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration oc if (hasAutonomousTesterConfiguration(octaneClient, octaneConfig, workspaceId)) { OctaneResponse response = executeGet(octaneClient, newUrl); - if (response != null && response.getStatus() == HttpStatus.SC_OK) { + 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 != null ? response.getStatus() : "(null)"); + logger.warn("Failed to retrieve MI Agent runs with au_tester_configuration, falling back to legacy runs query. Status: {}", response.getStatus()); } OctaneResponse response = executeGet(octaneClient, oldUrl); - if (response != null && response.getStatus() == HttpStatus.SC_OK) { + if (response.getStatus() == HttpStatus.SC_OK && response.getBody() != null) { return response.getBody(); } - throw new IllegalStateException("Failed to retrieve MI Agent manual runs from Octane. Status: " + (response != null ? response.getStatus() : "(null)")); + throw new IllegalStateException("Failed to retrieve MI Agent manual runs from Octane. Status: " + response.getStatus()); } private boolean hasAutonomousTesterConfiguration(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId) { String metadataUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_METADATA, octaneConfig.getSharedSpace(), workspaceId); OctaneResponse response = executeGet(octaneClient, metadataUrl); - return response != null && response.getStatus() == HttpStatus.SC_OK && response.getBody() != null && response.getBody().contains("au_tester_configuration"); + return response.getStatus() == HttpStatus.SC_OK && response.getBody() != null && response.getBody().contains("au_tester_configuration"); } private OctaneResponse executeGet(OctaneClient octaneClient, String url) { @@ -150,7 +147,8 @@ private OctaneResponse executeGet(OctaneClient octaneClient, String url) { .setUrl(url); try { - return octaneClient.getRestService().obtainOctaneRestClient().execute(request); + OctaneResponse response = octaneClient.getRestService().obtainOctaneRestClient().execute(request); + return Objects.requireNonNull(response, "Octane REST client returned null response"); } catch (IOException e) { throw new IllegalStateException("Failed to execute Octane request: " + url, e); } From 5ee4ff6eac57ee9d0e97eadd6a8c95fd47986bbc Mon Sep 17 00:00:00 2001 From: dradu Date: Fri, 31 Jul 2026 16:39:02 +0300 Subject: [PATCH 3/5] us #3198016: fix converter url strings --- .../converters/MfMIAgentConverter.java | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) 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 index d6a3b4dd..e79e6176 100644 --- 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 @@ -16,11 +16,13 @@ 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; @@ -39,11 +41,13 @@ public class MfMIAgentConverter extends TestsToRunConverter { private static final String MANUAL_RUN_DATA_PARAMETER = "manualRunData"; - private static final String GET_MANUAL_RUN_STEPS_URL_TEMPLATE = "/api/shared_spaces/%s/workspaces/%s/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}&limit=30&offset=0&order_by=order_in_suite_run,id&query=\"(parent_suite={id=%s};subtype IN 'run_manual')\""; + private static final String RUNS_PATH_TEMPLATE = "/api/shared_spaces/%s/workspaces/%s/runs"; - private static final String GET_MANUAL_RUN_STEPS_URL_TEMPLATE_WITH_AU_TESTER_CONFIG = "/api/shared_spaces/%s/workspaces/%s/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,au_tester_configuration,run_by{full_name},test{subtype}&limit=30&offset=0&order_by=order_in_suite_run,id&query=\"(parent_suite={id=%s};subtype IN 'run_manual')\""; + 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 GET_MANUAL_RUN_METADATA = "/api/shared_spaces/%s/workspaces/%s/metadata/fields?query=\"entity_name='run';name='au_tester_configuration'\""; + 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) { @@ -109,11 +113,10 @@ private OctaneClient getOctaneClient(Map globalParameters) { } private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId, String suiteRunId) { - String sharedSpaceId = octaneConfig.getSharedSpace(); - String oldUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_STEPS_URL_TEMPLATE, sharedSpaceId, workspaceId, suiteRunId); - String newUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_STEPS_URL_TEMPLATE_WITH_AU_TESTER_CONFIG, sharedSpaceId, workspaceId, 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(); @@ -121,6 +124,7 @@ private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration oc 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(); @@ -130,11 +134,37 @@ private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration oc } private boolean hasAutonomousTesterConfiguration(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId) { - String metadataUrl = octaneConfig.getUrl() + String.format(GET_MANUAL_RUN_METADATA, octaneConfig.getSharedSpace(), 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 IllegalStateException("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 IllegalStateException("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()); From dcfe3b0171b52e5c6f1cc8f9adab47606420e633 Mon Sep 17 00:00:00 2001 From: dradu Date: Tue, 4 Aug 2026 09:05:36 +0300 Subject: [PATCH 4/5] us #3198016: throw IllegalArgumentException --- .../converters/MfMIAgentConverter.java | 28 +++++++++---------- .../MfMIAgentConverterEnrichmentTest.java | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) 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 index e79e6176..6fc1230e 100644 --- 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 @@ -58,13 +58,13 @@ protected String convertInternal(List data, String executionDirec for (TestToRunData test : data) { String manualRunData = test.getParameter(MANUAL_RUN_DATA_PARAMETER); if (SdkStringUtils.isEmpty(manualRunData)) { - throw new IllegalStateException("Missing MI Agent run data for test '" + test.getTestName() + "'"); + throw new IllegalArgumentException("Missing MI Agent run data for test '" + test.getTestName() + "'"); } try { runs.add(OBJECT_MAPPER.readTree(manualRunData)); } catch (IOException e) { - throw new IllegalStateException("Invalid MI Agent run data for test '" + test.getTestName() + "'", e); + throw new IllegalArgumentException("Invalid MI Agent run data for test '" + test.getTestName() + "'", e); } } } @@ -74,7 +74,7 @@ protected String convertInternal(List data, String executionDirec try { return OBJECT_MAPPER.writeValueAsString(manifest); } catch (IOException e) { - throw new IllegalStateException("Failed to serialize MI Agent execution manifest", e); + throw new IllegalArgumentException("Failed to serialize MI Agent execution manifest", e); } } @@ -96,13 +96,13 @@ public void enrichTestsData(List tests, Map globa String runId = getRequiredRunId(test); JsonNode runNode = runsById.get(runId); if (runNode == null) { - throw new IllegalStateException("Failed to find MI Agent manual run '" + runId + "' for test '" + test.getTestName() + "'"); + 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 IllegalStateException("Failed to store MI Agent run data for test '" + test.getTestName() + "'", e); + throw new IllegalArgumentException("Failed to store MI Agent run data for test '" + test.getTestName() + "'", e); } } } @@ -130,7 +130,7 @@ private String fetchManualRuns(OctaneClient octaneClient, OctaneConfiguration oc return response.getBody(); } - throw new IllegalStateException("Failed to retrieve MI Agent manual runs from Octane. Status: " + response.getStatus()); + throw new IllegalArgumentException("Failed to retrieve MI Agent manual runs from Octane. Status: " + response.getStatus()); } private boolean hasAutonomousTesterConfiguration(OctaneClient octaneClient, OctaneConfiguration octaneConfig, String workspaceId) { @@ -146,7 +146,7 @@ private String buildMetadataUrl(OctaneConfiguration cfg, String workspaceId) { .build() .toString(); } catch (URISyntaxException e) { - throw new IllegalStateException("Failed to build metadata URL", e); + throw new IllegalArgumentException("Failed to build metadata URL", e); } } @@ -161,7 +161,7 @@ private String buildRunsUrl(OctaneConfiguration cfg, String workspaceId, String .build() .toString(); } catch (URISyntaxException e) { - throw new IllegalStateException("Failed to build runs URL", e); + throw new IllegalArgumentException("Failed to build runs URL", e); } } @@ -180,7 +180,7 @@ private OctaneResponse executeGet(OctaneClient octaneClient, String url) { OctaneResponse response = octaneClient.getRestService().obtainOctaneRestClient().execute(request); return Objects.requireNonNull(response, "Octane REST client returned null response"); } catch (IOException e) { - throw new IllegalStateException("Failed to execute Octane request: " + url, e); + throw new IllegalArgumentException("Failed to execute Octane request: " + url, e); } } @@ -189,7 +189,7 @@ private Map indexRunsById(String responseBody) { JsonNode root = OBJECT_MAPPER.readTree(responseBody); JsonNode runs = root.path("data"); if (!runs.isArray()) { - throw new IllegalStateException("Unexpected MI Agent runs response: missing data array"); + throw new IllegalArgumentException("Unexpected MI Agent runs response: missing data array"); } Map runsById = new HashMap<>(); @@ -201,26 +201,26 @@ private Map indexRunsById(String responseBody) { } return runsById; } catch (IOException e) { - throw new IllegalStateException("Failed to parse MI Agent runs response", 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 IllegalStateException("Missing runId parameter for MI Agent test '" + test.getTestName() + "'"); + 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 IllegalStateException("Missing global parameters required for MI Agent enrichment"); + throw new IllegalArgumentException("Missing global parameters required for MI Agent enrichment"); } String value = globalParameters.get(key); if (SdkStringUtils.isEmpty(value)) { - throw new IllegalStateException("Missing global parameter '" + key + "' required for MI Agent enrichment"); + 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 index 24ac6089..5e0208c7 100644 --- 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 @@ -168,8 +168,8 @@ public void enrichTestsData_missingRunId_throws() throws Exception { replay(mockClient, mockRestClient, mockRestService, mockConfigService); sdkClientsMap.put(realConfig, mockClient); - IllegalStateException ex = Assert.assertThrows( - IllegalStateException.class, + IllegalArgumentException ex = Assert.assertThrows( + IllegalArgumentException.class, () -> new MfMIAgentConverter().enrichTestsData(Collections.singletonList(test), globalParams()) ); From 001ab486ed75020d66da8fbd6b05ee32c322252d Mon Sep 17 00:00:00 2001 From: dradu Date: Tue, 4 Aug 2026 10:10:25 +0300 Subject: [PATCH 5/5] us #3198016: use actual data from octane --- .../MfMIAgentConverterEnrichmentTest.java | 105 +++++++++++++++--- .../executor/TestsToRunConverterTest.java | 21 +++- 2 files changed, 106 insertions(+), 20 deletions(-) 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 index 5e0208c7..d677626a 100644 --- 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 @@ -37,33 +37,89 @@ public class MfMIAgentConverterEnrichmentTest { " \"data\": [\n" + " {\n" + " \"type\": \"run\",\n" + - " \"id\": \"1042\",\n" + + " \"workspace_id\": 2001,\n" + + " \"name\": \"tsMIAgent\",\n" + " \"test_name\": \"Login flow\",\n" + - " \"subtype\": \"run_manual\",\n" + " \"order_in_suite_run\": 1,\n" + - " \"native_status\": {\"type\": \"list_node\", \"id\": \"status_ready\", \"name\": \"Ready\"},\n" + - " \"test\": {\"subtype\": \"test_manual\"},\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\": [{\"id\": \"s1\", \"step_type\": {\"name\": \"Normal\"}, \"description\": \"Open login page\"}]\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" + - " \"au_tester_configuration\": {\"BROWSER_NAME\": \"chrome\"}\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" + - " \"id\": \"1043\",\n" + + " \"workspace_id\": 2001,\n" + + " \"name\": \"tsMIAgent\",\n" + " \"test_name\": \"Checkout flow\",\n" + - " \"subtype\": \"run_manual\",\n" + " \"order_in_suite_run\": 2,\n" + - " \"native_status\": {\"type\": \"list_node\", \"id\": \"status_ready\", \"name\": \"Ready\"},\n" + - " \"test\": {\"subtype\": \"test_manual\"},\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" + - " {\"id\": \"s2\", \"step_type\": {\"name\": \"Normal\"}, \"description\": \"Add item\"},\n" + - " {\"id\": \"s3\", \"step_type\": {\"name\": \"Validate\"}, \"description\": \"Verify total\"}\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" + + " },\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" + @@ -122,18 +178,33 @@ public void enrichTestsData_happyPath() throws Exception { 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("chrome", run1.path("au_tester_configuration").path("BROWSER_NAME").asText()); + 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\",\"id\":\"1042\",\"test_name\":\"T1\",\"run_steps\":{\"data\":[],\"total_count\":0}}],\"total_count\":1}"; + 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"); @@ -151,6 +222,10 @@ public void enrichTestsData_fallbackWhenNoAuConfig() throws Exception { 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")); } 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 d5a3dbe9..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 @@ -75,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); @@ -116,19 +122,24 @@ public void miAgentConverterManifestTest() throws Exception { TestToRunData first = new TestToRunData() .setTestName("Login flow") .addParameters("runId", "1042") - .addParameters("manualRunData", "{\"type\":\"run\",\"id\":\"1042\",\"test_name\":\"Login flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":1,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":1,\"data\":[{\"id\":\"s1\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Open login page\"}]},\"au_tester_configuration\":{\"BROWSER_NAME\":\"chrome\"}}"); + .addParameters("manualRunData", MI_AGENT_RUN_1042); TestToRunData second = new TestToRunData() .setTestName("Checkout flow") .addParameters("runId", "1043") - .addParameters("manualRunData", "{\"type\":\"run\",\"id\":\"1043\",\"test_name\":\"Checkout flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":2,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":2,\"data\":[{\"id\":\"s2\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Add item\"},{\"id\":\"s3\",\"step_type\":{\"name\":\"Validate\"},\"description\":\"Verify total\"}]}}"); + .addParameters("manualRunData", MI_AGENT_RUN_1043); String actual = TestsToRunConvertersFactory.createConverter(MF_MI_AGENT) .convert(Arrays.asList(first, second), "", null) .getConvertedTestsString(); - String expected = "{\"data\":[{\"type\":\"run\",\"id\":\"1042\",\"test_name\":\"Login flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":1,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":1,\"data\":[{\"id\":\"s1\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Open login page\"}]},\"au_tester_configuration\":{\"BROWSER_NAME\":\"chrome\"}},{\"type\":\"run\",\"id\":\"1043\",\"test_name\":\"Checkout flow\",\"subtype\":\"run_manual\",\"order_in_suite_run\":2,\"native_status\":{\"type\":\"list_node\",\"id\":\"status_ready\",\"name\":\"Ready\"},\"test\":{\"subtype\":\"test_manual\"},\"run_steps\":{\"total_count\":2,\"data\":[{\"id\":\"s2\",\"step_type\":{\"name\":\"Normal\"},\"description\":\"Add item\"},{\"id\":\"s3\",\"step_type\":{\"name\":\"Validate\"},\"description\":\"Verify total\"}]}}],\"total_count\":2}"; - ObjectMapper objectMapper = new ObjectMapper(); - Assert.assertEquals(objectMapper.readTree(expected), objectMapper.readTree(actual)); + 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()); } }