From ac87e2b9a82a8aafd8038af30b52a9ee1c3b5acb Mon Sep 17 00:00:00 2001 From: ShubhamMour Date: Fri, 10 Jul 2026 12:48:40 +0530 Subject: [PATCH 1/5] Fix removeTestLog endpoint, add debug logging and Space Key wording - removeTestLog() now calls /api/v2/logs/{id} instead of /api/v2/run/{id}; it was deleting via the run endpoint with a log identifier, which failed with "Test Run not found". Verified against the live v2 API. - Add optional request-payload logging gated by VANSAH_DEBUG env var (or setDebug(true)) to aid diagnosing integration issues; attachments are logged after the payload so base64 does not flood the output. - Warning messages for an invalid/empty project key now say "Space Key" to match Vansah's terminology. --- src/main/java/com/vansah/VansahNode.java | 64 ++++++++++++++++++++---- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/vansah/VansahNode.java b/src/main/java/com/vansah/VansahNode.java index 80a021f..983ffab 100644 --- a/src/main/java/com/vansah/VansahNode.java +++ b/src/main/java/com/vansah/VansahNode.java @@ -148,7 +148,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."); } } /** @@ -188,6 +188,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 @@ -718,6 +750,7 @@ private void connectToVansahRest(String type) { requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); } @@ -731,6 +764,7 @@ private void connectToVansahRest(String type) { requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); } @@ -745,8 +779,9 @@ private void connectToVansahRest(String type) { 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()); @@ -755,17 +790,21 @@ private void connectToVansahRest(String type) { requestBody.accumulate("properties", properties()); } - requestBody.accumulate("project", jiraProjectAsset()); + requestBody.accumulate("project", jiraProjectAsset()); + emitPayload(getAddTestRunUrl(), requestBody); jsonRequestBody = Unirest.post(getAddTestRunUrl()).headers(headers).body(requestBody).asJson(); - } + } if(type == "addTestLog") { requestBody = addTestLogProp(); + requestBody.accumulate("project", jiraProjectAsset()); + + emitPayload(getAddTestLogUrl(), requestBody); + if(SEND_SCREENSHOT) { requestBody.append("attachments", addAttachment(FILE)); } - requestBody.accumulate("project", jiraProjectAsset()); jsonRequestBody = Unirest.post( getAddTestLogUrl()).headers(headers).body(requestBody).asJson(); } @@ -780,9 +819,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 +833,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 +847,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 +855,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(); } @@ -1068,7 +1112,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; From 98011d25fc5874d67df78cd7f9e72353fa222224 Mon Sep 17 00:00:00 2001 From: ShubhamMour Date: Fri, 10 Jul 2026 13:15:22 +0530 Subject: [PATCH 2/5] docs: fix README gaps and align with VansahNode API - Fix non-existent setter: setTESTFOLDER_PATH -> setFOLDERPATH (the documented example did not compile). - Fix swapped ATP/STP keys in the setter example (ATP KAN-P17, STP KAN-P18). - Document setProjectKey (Space Key), required for API v2 scoping. - Document setDebug / VANSAH_DEBUG payload logging. - Use "Space Key" terminology to match the binding and Vansah UI. - Correct addTestLog result values (NA/FAILED/PASSED/UNTESTED, not N/A) and clarify quick-test result is an int (0-3). - Tag the dependencies snippet as xml, and reformat the API-token notice. --- README.md | 59 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d43f35a..5e81c39 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)` @@ -358,12 +358,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 +417,17 @@ 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. - - + +### `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 +435,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 From eff1c201e21815236f53d3da5330e5e60c254a04 Mon Sep 17 00:00:00 2001 From: ShubhamMour Date: Mon, 13 Jul 2026 15:10:30 +0530 Subject: [PATCH 3/5] Create runs as UNTESTED and update pre-created step logs - All four addTestRun* methods (JIRA issue, test folder, ATP, STP) now send result id 3 (UNTESTED) on creation, so Vansah pre-creates one test log per step of the test case. - Capture the per-step logs from the add-test-run response (data.run.logs) into a step-number -> log-identifier map (storeStepLogs). - addTestLog now updates the pre-created step log via PUT /logs/{id} instead of posting a new one, which avoids the "A Test Log already exists for provided Step" error. Falls back to creating a log when no pre-created log exists for the step (e.g. a case with no steps), preserving prior behaviour. Verified against the live Vansah v2 API; existing test suite passes (18/18). --- src/main/java/com/vansah/VansahNode.java | 105 +++++++++++++++++++---- 1 file changed, 90 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/vansah/VansahNode.java b/src/main/java/com/vansah/VansahNode.java index 983ffab..c044d13 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; @@ -301,6 +302,20 @@ private static void emitPayload(String endpoint, JSONObject payload) { /** 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; @@ -748,6 +763,8 @@ 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); @@ -761,7 +778,9 @@ 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); @@ -776,7 +795,9 @@ 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); @@ -785,28 +806,51 @@ private void connectToVansahRest(String type) { 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()); } - + + // 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(); - requestBody.accumulate("project", jiraProjectAsset()); + 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)); + } - emitPayload(getAddTestLogUrl(), requestBody); + 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()); - if(SEND_SCREENSHOT) { + emitPayload(getAddTestLogUrl(), requestBody); - requestBody.append("attachments", addAttachment(FILE)); + 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(); + } } @@ -881,20 +925,28 @@ 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") { @@ -1273,6 +1325,29 @@ 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()); + } + } + } + private JSONObject addTestLogProp() { JSONObject testRun = new JSONObject(); From f217c3876fd3cb11af360065fa881df81cd08bd5 Mon Sep 17 00:00:00 2001 From: ShubhamMour Date: Mon, 13 Jul 2026 16:04:58 +0530 Subject: [PATCH 4/5] Recreate an UNTESTED step-log placeholder on removeTestLog After removeTestLog deletes a step's log, recreate an empty UNTESTED log for that step and refresh the step -> log map, so a later addTestLog updates the correct log and the map never holds a stale (deleted) identifier. - Add stepForLog(id): reverse lookup of the step for a given log identifier. - Add recreateUntestedStepLog(step): posts a fresh UNTESTED log for the step and stores its new identifier. - Keep the step -> log map in sync after every addTestLog (update and fallback-create paths). Verified against the live Vansah v2 API; existing test suite passes (18/18). --- src/main/java/com/vansah/VansahNode.java | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/main/java/com/vansah/VansahNode.java b/src/main/java/com/vansah/VansahNode.java index c044d13..66a0bf8 100644 --- a/src/main/java/com/vansah/VansahNode.java +++ b/src/main/java/com/vansah/VansahNode.java @@ -952,6 +952,22 @@ private void connectToVansahRest(String type) { 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{ @@ -1348,6 +1364,62 @@ private void storeStepLogs(JSONObject runObject) { } } + /** + * 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(); From 00eeea052914727ebbef93e4a38f662f3a4852e3 Mon Sep 17 00:00:00 2001 From: ShubhamMour Date: Mon, 13 Jul 2026 17:34:43 +0530 Subject: [PATCH 5/5] Add configurable test plan iteration (default 1) Add setTestPlanIteration(int) so Standard/Advanced Test Plan runs can target an iteration other than the hardcoded 1. Runs default to iteration 1 when the setter is not called, so existing callers are unaffected. Valid range is 1-5; out-of-range values are ignored with a warning. Document the setter and its default in the README. --- README.md | 15 +++++++++++++- src/main/java/com/vansah/VansahNode.java | 26 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5e81c39..32bcb6b 100644 --- a/README.md +++ b/README.md @@ -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. @@ -418,6 +422,15 @@ 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. diff --git a/src/main/java/com/vansah/VansahNode.java b/src/main/java/com/vansah/VansahNode.java index 66a0bf8..4416eb2 100644 --- a/src/main/java/com/vansah/VansahNode.java +++ b/src/main/java/com/vansah/VansahNode.java @@ -58,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. @@ -170,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. @@ -1230,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."); } @@ -1253,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."); }