diff --git a/README.md b/README.md index d43f35a..32bcb6b 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,12 @@
-API Token Update Required -Existing Vansah Connect tokens. You will need to generate a new API token to continue using integrations -All Connect-based integrations and bindings must be updated. Update your API configuration: {{apiUrl}}/api/v2/ - -Generate a new Vansah API token -Test your integrations to ensure continuity post-release +> ⚠️ **API Token Update Required** +> +> Existing Vansah Connect tokens must be regenerated to continue using integrations. All Connect-based integrations and bindings must be updated to the Vansah API v2 endpoint (`/api/v2/`). +> +> - Generate a new Vansah API token +> - Test your integrations to ensure continuity post-release > In the meantime, for more details check out: > - [Vansah for Jira — May 2026 Forge Release](https://help.vansah.com/en/articles/13298303-vansah-for-jira-may-2026) @@ -102,7 +102,7 @@ Test your integrations to ensure continuity post-release To Integrate Vansah Binding Java functions, you need to add the below dependencies into your pom.xml file. -```java +```xml @@ -211,7 +211,7 @@ To Integrate Vansah Binding Java functions, you need to add the below dependenci * Sample JUnit test class demonstrating how to send test execution results to Vansah. * * This class uses VansahNode to: - * - Connect to Vansah with a project key and token + * - Connect to Vansah with a Space Key (Jira project key) and token * - Send test results using: * 1. Test Folder path * 2. Advanced Test Plan (ATP) @@ -231,7 +231,7 @@ class Tests { // Test Case key from Jira private final String testCaseKey = "KAN-C17"; - // Jira Project key + // Space Key (the Jira project key) private final String projectKey = "KAN"; // Advanced Test Plan key used for ATP-based test executions @@ -245,8 +245,8 @@ class Tests { /** * Setup method that runs before each test. - * It initializes VansahNode with URL, API token, project key, folder path, - * and test plan keys for both ATP and STP. + * It initializes VansahNode with URL, API token, Space Key (Jira project key), + * folder path, and test plan keys for both ATP and STP. */ @SuppressWarnings("static-access") @BeforeEach @@ -313,7 +313,7 @@ Initiates a new test run within a specified test folder. Use this method to orga Logs the result of a specific test step, optionally including a comment and a screenshot. This method provides detailed tracking of test execution outcomes. - **Parameters**: - - `result`: The outcome of the test step (e.g., PASSED, FAILED)|| (e.g 0 = N/A, 1 = FAIL, 2 = PASS, 3 = Not tested). + - `result`: The outcome of the test step. Accepts either a **String** (`NA`, `FAILED`, `PASSED`, `UNTESTED` — case-insensitive) or an **int** (0 = N/A, 1 = FAIL, 2 = PASS, 3 = Not tested). Note the string is `NA`, not `N/A`. - `comment`: An optional comment describing the test step outcome. - `testStepRow`: The index of the test step within the test case. - `screenshotFile`: (Optional) The File Object of the screenshot taken to upload : Provide file object or Path of the screenshot. @@ -324,7 +324,7 @@ Quickly logs the overall result of a test case associated with either a JIRA iss - **Parameters**: - `testcase`: The test case identifier. - - `result`: The overall test result (e.g., PASS, FAIL). + - `result`: The overall test result as an **int** — 0 = N/A, 1 = FAIL, 2 = PASS, 3 = Not tested. ### `addTestRunFromAdvancedTestPlan(String testPlanAssetType, String testCaseKey)` @@ -335,7 +335,9 @@ Adds a new test run in Vansah under an Advanced Test Plan (ATP). The method link - `folder` – If the ATP is structured by test folder. - `issue` – If the ATP is linked to a specific Jira issue. - `testCaseKey`: The key of the test case to be executed (e.g., "KAN-C17"). - + +The run targets **iteration 1** by default. To target a different iteration, call `setTestPlanIteration(int)` (range 1–5) before this method. + ### `addTestRunFromStandardTestPlan(String testCaseKey)` Adds a new test run in Vansah under a Standard Test Plan (STP). This method links the specified test case to the configured Standard Test Plan, enabling execution tracking and reporting without the need for a specific folder or issue reference. @@ -343,6 +345,8 @@ Adds a new test run in Vansah under a Standard Test Plan (STP). This method link - **Parameters**: - `testCaseKey`: The key of the test case to be executed under the Standard Test Plan (e.g., "KAN-C17"). +The run targets **iteration 1** by default. To target a different iteration, call `setTestPlanIteration(int)` (range 1–5) before this method. + ### `removeTestRun()` and `removeTestLog()` Deletes a previously created test run or log. These methods are useful for cleaning up data in Vansah that is no longer relevant or was created in error. @@ -358,12 +362,21 @@ Updates an existing test log with new information, such as a revised result or a The `VansahNode` class provides a set of setter methods to configure your test management context before performing operations such as creating test runs, adding test logs, and more. Here's a detailed overview of each setter method: -### `setTESTFOLDER_PATH(String TESTFOLDER_PATH)` +### `setProjectKey(String projectKey)` + +Sets the **Space Key** (the Jira project key) that scopes your test runs and logs. This is **required for the Vansah API v2** — it is sent as the top-level `project` object on every request. + +- **Parameters**: + - `projectKey`: The Space Key / Jira project key (e.g., `"KAN"`). + +> **Note:** A null or empty value prints a warning (`⚠️ Warning: Provided Space Key is null or empty. Value not updated.`) and is ignored. + +### `setFOLDERPATH(String FOLDERPATH)` Configures the **Test Folder Path** for the VansahNode instance. This path is essential for associating your test runs and logs with the correct test folder structure in Vansah. - **Parameters**: - - `TESTFOLDER_PATH`: The folder path for the test folder in Vansah. The path must contain at least one `/` and must not start with `/`. + - `FOLDERPATH`: The folder path for the test folder in Vansah. The path must contain at least one `/` and must not start with `/`. > **Note:** Invalid paths (e.g., those starting with `/` or lacking `/`) will print a warning and be ignored. @@ -408,8 +421,26 @@ Sets the key for the Standard Test Plan (STP). This key is used to associate tes - **Parameters**: - `testPlanKey`: The unique key of the Standard Test Plan (e.g., "KAN-P17"), used to organize and manage test executions in Vansah. - - + +### `setTestPlanIteration(int iteration)` + +Sets the **iteration** to target when creating a run from a Standard or Advanced Test Plan. This is **optional** — if you never call it, runs default to **iteration 1**. Only call this when you need to record results against a specific iteration of the test plan. + +- **Parameters**: + - `iteration`: The test plan iteration to target. Valid range is **1–5**. Values outside this range are ignored (a warning is printed and the default of 1 is kept). + +> **Note:** The iteration applies only to `addTestRunFromStandardTestPlan(...)` and `addTestRunFromAdvancedTestPlan(...)`. It has no effect on Jira-issue or test-folder runs. + +### `setDebug(boolean debug)` + +Enables or disables **request-payload logging**. When enabled, the exact JSON body sent to Vansah is printed before each API call — useful for diagnosing issues such as a malformed folder path or a missing Space Key. Screenshot attachments are logged after the payload so base64 data does not flood the output. + +Debug logging is also enabled automatically when the `VANSAH_DEBUG` environment variable is set to `true` or `1`. + +- **Parameters**: + - `debug`: `true` to log outgoing request payloads, `false` to disable. + + ### Usage To use these setter methods in your application, create an instance of `VansahNode` and call the relevant setter methods with the appropriate values before proceeding with any test management operations. For example: @@ -417,13 +448,14 @@ To use these setter methods in your application, create an instance of `VansahNo ```java VansahNode vansahNode = new VansahNode(); vansahNode.setVansahToken("Add your Token here"); -vansahNode.setTESTFOLDER_PATH("feature-tests/login"); +vansahNode.setProjectKey("KAN"); // Space Key (the Jira project key) — required for API v2 +vansahNode.setFOLDERPATH("feature-tests/login"); vansahNode.setJIRA_ISSUE_KEY("your-jira-issue-key"); vansahNode.setSPRINT_NAME("your-sprint-name"); vansahNode.setRELEASE_NAME("your-release-name"); vansahNode.setENVIRONMENT_NAME("your-environment-name"); -vansahNode.setAdvancedTestPlanKey("KAN-P18"); -vansahNode.setStandardTestPlanKey("KAN-P17"); +vansahNode.setAdvancedTestPlanKey("KAN-P17"); +vansahNode.setStandardTestPlanKey("KAN-P18"); ``` ## Developed By diff --git a/src/main/java/com/vansah/VansahNode.java b/src/main/java/com/vansah/VansahNode.java index 80a021f..4416eb2 100644 --- a/src/main/java/com/vansah/VansahNode.java +++ b/src/main/java/com/vansah/VansahNode.java @@ -9,6 +9,7 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.json.JSONArray; import org.json.JSONObject; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.JsonNode; @@ -57,6 +58,14 @@ public class VansahNode { */ private static String STANDARD_TEST_PLAN_KEY = null; + /** + * The iteration number for a Standard or Advanced Test Plan run. + * Defaults to {@code 1} (Vansah's first iteration). Only sent to the API when the + * caller explicitly overrides it via {@link #setTestPlanIteration(int)}; otherwise + * the default iteration of 1 is used. Valid range is 1–5. + */ + private static Integer TEST_PLAN_ITERATION = null; + /** * Returns the endpoint URL for adding a test run. * This dynamically constructs the URL using the current Vansah base URL and API version. @@ -148,7 +157,7 @@ public static void setProjectKey(String projectKey) { if (projectKey != null && !projectKey.trim().isEmpty()) { PROJECT_KEY = projectKey.trim(); } else { - System.out.println("⚠️ Warning: Provided project key is null or empty. Value not updated."); + System.out.println("⚠️ Warning: Provided Space Key is null or empty. Value not updated."); } } /** @@ -169,6 +178,20 @@ public void setAdvancedTestPlanKey(String ADVANCED_TEST_PLAN_KEY) { public void setStandardTestPlanKey(String STANDARD_TEST_PLAN_KEY) { this.STANDARD_TEST_PLAN_KEY = STANDARD_TEST_PLAN_KEY; } + /** + * Sets the iteration number for a Standard or Advanced Test Plan run. + * If this is never called, the run defaults to iteration 1. Only call this when you + * need to target a specific iteration of the test plan. + * + * @param iteration The test plan iteration to target. Valid range is 1–5. + */ + public void setTestPlanIteration(int iteration) { + if (iteration >= 1 && iteration <= 5) { + this.TEST_PLAN_ITERATION = iteration; + } else { + System.out.println("⚠️ Warning: Test plan iteration must be between 1 and 5. Value not updated (default is 1)."); + } + } /** * The authentication token required for making requests to the Vansah API. This token * authenticates the client to the Vansah system, ensuring secure access to API functions. @@ -188,6 +211,38 @@ public void setStandardTestPlanKey(String STANDARD_TEST_PLAN_KEY) { public static void setVansahToken(String vansahToken) { VANSAH_TOKEN = vansahToken; } + + /** + * Controls whether outgoing request payloads are logged before being sent to Vansah. + * Useful for diagnosing integration issues (e.g. malformed folder paths, missing + * project keys) without inspecting network traffic. Defaults to enabled when the + * VANSAH_DEBUG environment variable is set to "true" or "1". + */ + private static boolean DEBUG = "true".equalsIgnoreCase(System.getenv("VANSAH_DEBUG")) + || "1".equals(System.getenv("VANSAH_DEBUG")); + + /** + * Enables or disables debug payload logging for outgoing Vansah API requests. + * + * @param debug true to log request payloads before they are sent, false to disable. + */ + public static void setDebug(boolean debug) { + DEBUG = debug; + } + + /** + * Logs the outgoing request payload for a given endpoint when debug mode is enabled. + * Called before any base64 attachment is added to the payload so the log isn't + * flooded with encoded file data. + * + * @param endpoint The Vansah API endpoint being called. + * @param payload The JSON request body about to be sent. + */ + private static void emitPayload(String endpoint, JSONObject payload) { + if (DEBUG) { + System.out.println("🐛 [DEBUG] Request to " + endpoint + ": " + payload.toString()); + } + } /** * The hostname or IP address of the proxy server used when the Vansah API binding operates behind a proxy. * Specify the address of the proxy to enable communication with the Vansah API through it. Leave blank if @@ -269,6 +324,20 @@ public static void setVansahToken(String vansahToken) { /** A mapping from result names to their corresponding numeric codes. */ private HashMap resultAsName = new HashMap<>(); + /** + * The result id sent by default when a test run is created. Creating a run as UNTESTED (id 3) + * makes Vansah pre-create one UNTESTED test log per step of the test case; those per-step logs + * are then updated (rather than newly created) by {@link #addTestLog}. + */ + private static final int UNTESTED_RESULT_ID = 3; + + /** + * Maps a test step number to the identifier of the (UNTESTED) test log Vansah pre-creates for + * that step when a run is created. Populated from the "logs" array of the add-test-run response + * and used by {@link #addTestLog} to update the correct step log. + */ + private Map stepLogIdentifiers = new HashMap<>(); + /** The JSON object representing the body of the API request. */ private JSONObject requestBody = null; @@ -716,8 +785,11 @@ private void connectToVansahRest(String type) { requestBody.accumulate("properties", properties()); } + // Create the run as UNTESTED so Vansah pre-creates a log for each step. + requestBody.accumulate("result", resultObj(UNTESTED_RESULT_ID)); requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); } @@ -728,9 +800,12 @@ private void connectToVansahRest(String type) { if(properties().length()!=0) { requestBody.accumulate("properties", properties()); } - + + // Create the run as UNTESTED so Vansah pre-creates a log for each step. + requestBody.accumulate("result", resultObj(UNTESTED_RESULT_ID)); requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); } @@ -742,32 +817,62 @@ private void connectToVansahRest(String type) { if(properties().length()!=0) { requestBody.accumulate("properties", properties()); } - + + // Create the run as UNTESTED so Vansah pre-creates a log for each step. + requestBody.accumulate("result", resultObj(UNTESTED_RESULT_ID)); requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); - } + } if(type == "addTestRunFromStandardTestPlan") { requestBody = new JSONObject(); requestBody.accumulate("case", testCase()); - requestBody.accumulate("asset", standardTestPlanAsset()); + requestBody.accumulate("asset", standardTestPlanAsset()); if(properties().length()!=0) { requestBody.accumulate("properties", properties()); } - - requestBody.accumulate("project", jiraProjectAsset()); + + // Create the run as UNTESTED so Vansah pre-creates a log for each step. + requestBody.accumulate("result", resultObj(UNTESTED_RESULT_ID)); + requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); - } + } if(type == "addTestLog") { - requestBody = addTestLogProp(); - if(SEND_SCREENSHOT) { + String stepLogIdentifier = stepLogIdentifiers.get(STEP_ORDER); + if (stepLogIdentifier != null) { + // A log for this step was pre-created (UNTESTED) when the run was created. + // Update it in place rather than posting a new one (which Vansah rejects + // with "A Test Log already exists for provided Step."). + requestBody = new JSONObject(); + requestBody.accumulate("result", resultObj(RESULT_KEY)); + requestBody.accumulate("actualResult", COMMENT); + requestBody.accumulate("project", jiraProjectAsset()); + + emitPayload(getUpdateTestLogUrl(stepLogIdentifier), requestBody); + + if(SEND_SCREENSHOT) { + requestBody.append("attachments", addAttachment(FILE)); + } - requestBody.append("attachments", addAttachment(FILE)); + jsonRequestBody = Unirest.put(getUpdateTestLogUrl(stepLogIdentifier)).headers(headers).body(requestBody).asJson(); + TEST_LOG_IDENTIFIER = stepLogIdentifier; + } else { + // Fallback: no pre-created log for this step (e.g. the case has no steps) -- create one. + requestBody = addTestLogProp(); + requestBody.accumulate("project", jiraProjectAsset()); - } - requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestLogUrl(), requestBody); + + if(SEND_SCREENSHOT) { + + requestBody.append("attachments", addAttachment(FILE)); + + } - jsonRequestBody = Unirest.post( getAddTestLogUrl()).headers(headers).body(requestBody).asJson(); + jsonRequestBody = Unirest.post( getAddTestLogUrl()).headers(headers).body(requestBody).asJson(); + } } @@ -780,9 +885,10 @@ private void connectToVansahRest(String type) { requestBody.accumulate("properties", properties()); } requestBody.accumulate("result", resultObj(RESULT_KEY)); - + requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); } if(type == "addQuickTestFromTestFolders") { @@ -793,9 +899,10 @@ private void connectToVansahRest(String type) { requestBody.accumulate("properties", properties()); } requestBody.accumulate("result", resultObj(RESULT_KEY)); - + requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); } @@ -806,7 +913,7 @@ private void connectToVansahRest(String type) { if(type == "removeTestLog") { - jsonRequestBody = Unirest.delete(getRemoveTestRunUrl(TEST_LOG_IDENTIFIER)).headers(headers).asJson(); + jsonRequestBody = Unirest.delete(getRemoveTestLogUrl(TEST_LOG_IDENTIFIER)).headers(headers).asJson(); } @@ -814,10 +921,13 @@ private void connectToVansahRest(String type) { requestBody = new JSONObject(); requestBody.accumulate("result", resultObj(RESULT_KEY)); requestBody.accumulate("actualResult", COMMENT); + requestBody.accumulate("project", jiraProjectAsset()); + + emitPayload(getUpdateTestLogUrl(TEST_LOG_IDENTIFIER), requestBody); + if(SEND_SCREENSHOT) { requestBody.append("attachments", addAttachment(FILE)); } - requestBody.accumulate("project", jiraProjectAsset()); jsonRequestBody = Unirest.put(getUpdateTestLogUrl(TEST_LOG_IDENTIFIER)).headers(headers).body(requestBody).asJson(); } @@ -837,25 +947,49 @@ private void connectToVansahRest(String type) { if (success){ if(type == "addTestRunFromJIRAIssue") { - TEST_RUN_IDENTIFIER = fullBody.getJSONObject("data").getJSONObject("run").get("identifier").toString(); + JSONObject runObject = fullBody.getJSONObject("data").getJSONObject("run"); + TEST_RUN_IDENTIFIER = runObject.get("identifier").toString(); System.out.println("Test Run Identifier: " + TEST_RUN_IDENTIFIER); + storeStepLogs(runObject); } if(type == "addTestRunFromTestFolder") { - TEST_RUN_IDENTIFIER = fullBody.getJSONObject("data").getJSONObject("run").get("identifier").toString(); + JSONObject runObject = fullBody.getJSONObject("data").getJSONObject("run"); + TEST_RUN_IDENTIFIER = runObject.get("identifier").toString(); System.out.println("Test Run Identifier: " + TEST_RUN_IDENTIFIER); + storeStepLogs(runObject); } if(type == "addTestRunFromAdvancedTestPlan") { - TEST_RUN_IDENTIFIER = fullBody.getJSONObject("data").getJSONObject("run").get("identifier").toString(); + JSONObject runObject = fullBody.getJSONObject("data").getJSONObject("run"); + TEST_RUN_IDENTIFIER = runObject.get("identifier").toString(); System.out.println("Test Run Identifier: " + TEST_RUN_IDENTIFIER); + storeStepLogs(runObject); } if(type == "addTestRunFromStandardTestPlan") { - TEST_RUN_IDENTIFIER = fullBody.getJSONObject("data").getJSONObject("run").get("identifier").toString(); + JSONObject runObject = fullBody.getJSONObject("data").getJSONObject("run"); + TEST_RUN_IDENTIFIER = runObject.get("identifier").toString(); System.out.println("Test Run Identifier: " + TEST_RUN_IDENTIFIER); + storeStepLogs(runObject); } if(type == "addTestLog") { TEST_LOG_IDENTIFIER = fullBody.getJSONObject("data").getJSONObject("log").get("identifier").toString(); System.out.println("Test Log Identifier: " + TEST_LOG_IDENTIFIER); + // Keep the step -> log map current for both the update and fallback-create paths. + stepLogIdentifiers.put(STEP_ORDER, TEST_LOG_IDENTIFIER); + } + + if(type == "removeTestLog") { + // The deleted log leaves its step without a log. Recreate an empty UNTESTED + // placeholder for that step so a later addTestLog updates the correct log and + // the stored step -> log map never holds a stale (deleted) identifier. + Integer removedStep = stepForLog(TEST_LOG_IDENTIFIER); + if (removedStep != null) { + stepLogIdentifiers.remove(removedStep); + } + TEST_LOG_IDENTIFIER = null; + if (removedStep != null) { + recreateUntestedStepLog(removedStep); + } } }else{ @@ -1068,7 +1202,7 @@ private JSONObject jiraProjectAsset() { } else { // Print warning if key is invalid - System.out.println("⚠️ Warning: Please provide a valid JIRA Project Key."); + System.out.println("⚠️ Warning: Please provide a valid Space Key."); } return asset; @@ -1118,7 +1252,7 @@ private JSONObject advancedTestPlanAsset() { if (ADVANCED_TEST_PLAN_KEY != null && !ADVANCED_TEST_PLAN_KEY.trim().isEmpty()) { asset.accumulate("type", "plannedRun"); asset.accumulate("key", ADVANCED_TEST_PLAN_KEY); - asset.accumulate("iteration", 1); // Optionally, use a variable like ATP_ITERATION + asset.accumulate("iteration", TEST_PLAN_ITERATION != null ? TEST_PLAN_ITERATION : 1); } else { System.out.println("⚠️ Warning: Please provide a valid Advanced Test Plan Key."); } @@ -1141,7 +1275,7 @@ private JSONObject standardTestPlanAsset() { if (STANDARD_TEST_PLAN_KEY != null && !STANDARD_TEST_PLAN_KEY.trim().isEmpty()) { asset.accumulate("type", "plannedRun"); asset.accumulate("key", STANDARD_TEST_PLAN_KEY); - asset.accumulate("iteration", 1); + asset.accumulate("iteration", TEST_PLAN_ITERATION != null ? TEST_PLAN_ITERATION : 1); } else { System.out.println("⚠️ Warning: Please provide a valid Standard Test Plan Key."); } @@ -1229,6 +1363,85 @@ public boolean isValidFolderPath(String folderPath) { * step number, result ID, and actual result comment. This object can be directly used as the body * of an API request to add or update test logs. */ + /** + * Records the per-step test logs that Vansah pre-creates when a run is created as UNTESTED. + * Reads the "logs" array from the add-test-run response and maps each step number to its log + * identifier so {@link #addTestLog} can update the correct step log instead of creating a new one. + * Any previously stored mapping is cleared first, so each new run starts fresh. + * + * @param runObject The "run" object from the add-test-run response body (data.run). + */ + private void storeStepLogs(JSONObject runObject) { + stepLogIdentifiers.clear(); + if (runObject == null || !runObject.has("logs")) { + return; + } + JSONArray logs = runObject.getJSONArray("logs"); + for (int i = 0; i < logs.length(); i++) { + JSONObject log = logs.getJSONObject(i); + if (log.has("identifier") && log.has("step") && log.getJSONObject("step").has("number")) { + stepLogIdentifiers.put(log.getJSONObject("step").getInt("number"), + log.get("identifier").toString()); + } + } + } + + /** + * Reverse lookup: returns the step number currently mapped to the given log identifier, or + * null if no step maps to it. + * + * @param logIdentifier The test log identifier to look up. + * @return The step number whose log is {@code logIdentifier}, or null if not found. + */ + private Integer stepForLog(String logIdentifier) { + if (logIdentifier == null) { + return null; + } + for (Map.Entry entry : stepLogIdentifiers.entrySet()) { + if (logIdentifier.equals(entry.getValue())) { + return entry.getKey(); + } + } + return null; + } + + /** + * Recreates an empty UNTESTED test log for the given step on the current run and stores its + * identifier in the step -> log map. Called after {@link #removeTestLog()} so the step keeps a + * placeholder log that a later {@link #addTestLog} can update, and the map never holds a stale id. + * + * @param stepNumber The step number whose log placeholder should be recreated. + */ + private void recreateUntestedStepLog(int stepNumber) { + try { + JSONObject run = new JSONObject(); + run.accumulate("identifier", TEST_RUN_IDENTIFIER); + + JSONObject step = new JSONObject(); + step.accumulate("number", stepNumber); + + JSONObject body = new JSONObject(); + body.accumulate("run", run); + body.accumulate("step", step); + body.accumulate("result", resultObj(UNTESTED_RESULT_ID)); + body.accumulate("project", jiraProjectAsset()); + + emitPayload(getAddTestLogUrl(), body); + HttpResponse response = Unirest.post(getAddTestLogUrl()).headers(headers).body(body).asJson(); + JSONObject responseObject = new JSONObject(response.getBody().toString()); + + if (responseObject.getBoolean("success")) { + String newLogIdentifier = responseObject.getJSONObject("data").getJSONObject("log").get("identifier").toString(); + stepLogIdentifiers.put(stepNumber, newLogIdentifier); + System.out.println("Recreated UNTESTED log for step " + stepNumber + ": " + newLogIdentifier); + } else { + System.out.println("Could not recreate a log for step " + stepNumber + ": " + responseObject.getString("message")); + } + } catch (Exception e) { + System.out.println("Error recreating a log for step " + stepNumber + ": " + e.toString()); + } + } + private JSONObject addTestLogProp() { JSONObject testRun = new JSONObject();