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();