diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5b0dae2 --- /dev/null +++ b/.env.example @@ -0,0 +1,42 @@ +# Copy this file to `.env` and fill in real values before running VansahNodeFullTests. +# `.env` is already listed in .gitignore -- never commit real tokens. + +# Base URL of your Vansah instance. Defaults to https://prod.vansah.com if unset. +VANSAH_URL= + +# Vansah Connect token (Vansah Test Management > Connect). Required to run any test -- +# all tests are skipped (not failed) if this is blank. +VANSAH_TOKEN= + +# Jira project key that owns the test folder / issue below, e.g. "DEMO". +VANSAH_PROJECT_KEY= + +# Path of an existing Vansah test folder, e.g. "regression/2025/smoke". +VANSAH_FOLDER_PATH= + +# Key of an existing Jira issue to attach test runs to, e.g. "DEMO-1". +VANSAH_JIRA_ISSUE_KEY= + +# Key of an existing test case, e.g. "DEMO-C1". +VANSAH_TEST_CASE_KEY= + +# Key of an existing Advanced Test Plan, e.g. "DEMO-P1". +VANSAH_ATP_KEY= + +# Asset type backing the Advanced Test Plan's requirement: "folder" or "issue". Defaults to "folder". +VANSAH_ATP_ASSET_TYPE= + +# Key of an existing Standard Test Plan, e.g. "DEMO-P2". +VANSAH_STP_KEY= + +# Optional test run properties. +VANSAH_SPRINT_NAME= +VANSAH_RELEASE_NAME= +VANSAH_ENVIRONMENT_NAME= + +# Optional path to a real screenshot/image file to use for attachment tests. +# If unset, a tiny generated placeholder PNG is used instead. +VANSAH_SCREENSHOT_PATH= + +# Optional: true/1 to print outgoing request payloads (mirrors VansahNode.setDebug()). +VANSAH_DEBUG=false diff --git a/.gitignore b/.gitignore index 05fef09..00cdef6 100644 --- a/.gitignore +++ b/.gitignore @@ -57,7 +57,7 @@ $RECYCLE.BIN/ # Node modules (if using Node.js in the project, e.g., for frontend work) node_modules/ -# Environment files +# Environment files (real secrets stay local; .env.example is committed as a template) .env .env.local 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/license.md b/license.md index b79c981..30cfc00 100644 --- a/license.md +++ b/license.md @@ -1,5 +1,5 @@
- +

diff --git a/src/main/java/com/vansah/VansahNode.java b/src/main/java/com/vansah/VansahNode.java index 80a021f..a6a4f48 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. @@ -110,16 +119,6 @@ private static String getRemoveTestRunUrl(String identifier) { return VANSAH_URL + "/api/" + API_VERSION + "/run/" + identifier; } - /** - * Returns the endpoint URL for retrieving test scripts associated with a test case. - * This dynamically builds the URL to query test scripts by case key. - * - * @return The complete URL for the "get test script list" API call. - */ - private static String getTestScriptUrl() { - return VANSAH_URL + "/api/" + API_VERSION + "/testCase/list/testScripts"; - } - /** * Sets a custom base URL for the Vansah API. *

@@ -148,7 +147,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 +168,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 +201,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 +314,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; @@ -594,72 +653,6 @@ public void updateTestLog(String result, String comment, File image) throws Exce connectToVansahRest("updateTestLog"); } - /** - * Retrieves the count of test steps for a given test case from Vansah. This method sends a GET request - * to the Vansah API to fetch the test script associated with the specified case key and calculates the - * number of steps in the test script. It also handles proxy settings if specified. - * - * @param case_key The unique identifier for the test case whose test script step count is to be retrieved. - * @return The number of test steps contained within the test script for the specified case key. Returns 0 - * if there's an error in fetching the test script, the response from Vansah is unexpected, or the - * specified test case does not contain any steps. - * @throws Exception if there's an issue with network connectivity, parsing the response, or if the Vansah - * API endpoint is not reachable. In such cases, the exception is caught and printed to the - * console, and the method returns 0. - * - * Note: This method assumes that the Vansah API token and possibly proxy settings have been correctly set - * prior to its invocation. It utilizes the `headers` and `clientBuilder` fields of the enclosing class - * to configure the HTTP request. - */ - public int testStepCount(String case_key) { - - - try { - headers.put("Authorization",VANSAH_TOKEN); - headers.put("Content-Type","application/json"); - - clientBuilder = HttpClientBuilder.create(); - // Detecting if the system using any proxy setting. - - - if (hostAddr.equals("") && portNo.equals("")) { - Unirest.setHttpClient(clientBuilder.build()); - } else { - System.out.println("Proxy Server"); - credsProvider = new BasicCredentialsProvider(); - clientBuilder.useSystemProperties(); - clientBuilder.setProxy(new HttpHost(hostAddr, Integer.parseInt(portNo))); - clientBuilder.setDefaultCredentialsProvider(credsProvider); - clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - Unirest.setHttpClient(clientBuilder.build()); - } - HttpResponse get; - get = Unirest.get(getTestScriptUrl()).headers(headers).queryString("caseKey", case_key).asJson(); - if (get.getBody().toString().equals("[]")) { - System.out.println("Unexpected Response From Server: " + get.getBody().toString()); - } else { - JSONObject jsonobjInit = new JSONObject(get.getBody().toString()); - boolean success = jsonobjInit.getBoolean("success"); - String vansah_message = jsonobjInit.getString("message"); - - if (success) { - - int testRows = jsonobjInit.getJSONObject("data").getJSONArray("steps").length(); - System.out.println("NUMBER OF STEPS: " + testRows); - return testRows; - - } else { - System.out.println("Error - Response From Vansah: " + vansah_message); - return 0; - } - } - } catch (Exception ex) { - ex.printStackTrace(); - } - return 0; - - } - /** * Central method for interacting with the Vansah REST API. Depending on the specified type, it constructs * and executes different API requests to add, update, or remove test runs and logs. This method also handles @@ -716,8 +709,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 +724,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 +741,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) { - jsonRequestBody = Unirest.post( getAddTestLogUrl()).headers(headers).body(requestBody).asJson(); + requestBody.append("attachments", addAttachment(FILE)); + + } + + jsonRequestBody = Unirest.post( getAddTestLogUrl()).headers(headers).body(requestBody).asJson(); + } } @@ -780,9 +809,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 +823,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 +837,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 +845,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 +871,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 +1126,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 +1176,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 +1199,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 +1287,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(); diff --git a/src/test/java/com/vansah/Tests.java b/src/test/java/com/vansah/Tests.java deleted file mode 100644 index b2abbb7..0000000 --- a/src/test/java/com/vansah/Tests.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.vansah; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class Tests { - - private final VansahNode sendResults = new VansahNode(); - - private final String vansahURL = "https://prodau.vansah.com"; - - private final String testfolderPath = "vansah test automation/regression 2025/"; - - private final String testCaseKey = "KAN-C17"; - - private final String projectKey = "KAN"; - - private final String testPlanKeyforATP = "KAN-P17"; - - private final String testPlanAssetType = "folder"; //or issue only for ATP - Advanced Test Plan - - private final String testPlanKeyforSTP = "KAN-P18"; //required for Standard Test Plan - - - @SuppressWarnings("static-access") - @BeforeEach - void setup() { - - sendResults.setVansahURL(vansahURL); - - sendResults.setVansahToken(System.getenv("CONNECT_DEMO_TOKEN")); - - sendResults.setProjectKey(projectKey); - - sendResults.setFOLDERPATH(testfolderPath); - - sendResults.setAdvancedTestPlanKey(testPlanKeyforATP); - - sendResults.setStandardTestPlanKey(testPlanKeyforSTP); - - - } - - @Test - void sendingResultstoVansah_usingTestFolderPath() throws Exception { - - sendResults.addTestRunFromTestFolder(testCaseKey); - - sendResults.addTestLog("passed", "Actual result for the Test Step", 1); - - - } - - @Test - void sendingResultstoVansahforATP() throws Exception { - - sendResults.addTestRunFromAdvancedTestPlan(testPlanAssetType,testCaseKey); - - sendResults.addTestLog("passed", "Actual result for the Test Step", 1); - - - } - - @Test - void sendingResultstoVansahforSTP() throws Exception { - - sendResults.addTestRunFromStandardTestPlan(testCaseKey); - - sendResults.addTestLog("passed", "Actual result for the Test Step", 1); - - - } - -} diff --git a/src/test/java/com/vansah/VansahNodeFullTests.java b/src/test/java/com/vansah/VansahNodeFullTests.java new file mode 100644 index 0000000..97a6906 --- /dev/null +++ b/src/test/java/com/vansah/VansahNodeFullTests.java @@ -0,0 +1,381 @@ +package com.vansah; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * ============================================================================ + * FOR TESTING THE VANSAH JAVA BINDING (VansahNode) ONLY. + * ============================================================================ + * + * This is a single, self-contained live test suite that exercises every public + * VansahNode operation (create runs, add/update/remove step logs, attachments, + * quick tests, Standard/Advanced Test Plans and the config/validation helpers) + * against a REAL Vansah instance. It is shipped as a reference/example so you + * can confirm the binding works end-to-end in your own Vansah workspace. + * + * How to run: + * 1. Copy .env.example to .env at the project root and fill in real values. + * (.env is gitignored - never commit real tokens.) You may instead export + * the same keys as environment variables (handy for CI). + * 2. mvn test + * + * Required keys (see .env.example): VANSAH_TOKEN, VANSAH_PROJECT_KEY, + * VANSAH_JIRA_ISSUE_KEY, VANSAH_TEST_CASE_KEY, VANSAH_FOLDER_PATH, + * VANSAH_ATP_KEY, VANSAH_STP_KEY. Optional: VANSAH_URL, VANSAH_ATP_ASSET_TYPE, + * VANSAH_SPRINT_NAME, VANSAH_RELEASE_NAME, VANSAH_ENVIRONMENT_NAME, + * VANSAH_SCREENSHOT_PATH, VANSAH_DEBUG. + * + * Any test whose required configuration is missing is SKIPPED (not failed), so + * this suite is safe to run before .env is fully filled in - with no config it + * simply skips every network test. + * + * Note on assertions: VansahNode catches all API/network errors internally and + * only prints outcomes to stdout - it does not expose success/failure or the + * created run/log ids. These tests therefore assert that each call completes + * without throwing; they cannot tell a real Vansah success from a silently + * logged API error. Enable VANSAH_DEBUG=true to inspect the exact JSON sent. + * Every network test cleans up after itself (removeTestLog / removeTestRun). + */ +class VansahNodeFullTests { + + private static String vansahUrl; + private static String token; + private static String projectKey; + private static String folderPath; + private static String jiraIssueKey; + private static String testCaseKey; + private static String atpKey; + private static String atpAssetType; + private static String stpKey; + private static String sprintName; + private static String releaseName; + private static String environmentName; + private static File screenshotFile; + + private VansahNode node; + + @BeforeAll + static void loadConfig() throws IOException { + vansahUrl = Env.get("VANSAH_URL", "https://prod.vansah.com"); + token = Env.get("VANSAH_TOKEN"); + projectKey = Env.get("VANSAH_PROJECT_KEY"); + folderPath = Env.get("VANSAH_FOLDER_PATH"); + jiraIssueKey = Env.get("VANSAH_JIRA_ISSUE_KEY"); + testCaseKey = Env.get("VANSAH_TEST_CASE_KEY"); + atpKey = Env.get("VANSAH_ATP_KEY"); + atpAssetType = Env.get("VANSAH_ATP_ASSET_TYPE", "folder"); + stpKey = Env.get("VANSAH_STP_KEY"); + sprintName = Env.get("VANSAH_SPRINT_NAME"); + releaseName = Env.get("VANSAH_RELEASE_NAME"); + environmentName = Env.get("VANSAH_ENVIRONMENT_NAME"); + screenshotFile = resolveScreenshotFile(); + } + + private static File resolveScreenshotFile() throws IOException { + String configuredPath = Env.get("VANSAH_SCREENSHOT_PATH"); + if (configuredPath != null) { + File configured = new File(configuredPath); + if (configured.isFile()) { + return configured; + } + System.out.println("⚠️ VANSAH_SCREENSHOT_PATH does not point to a file, using a generated placeholder instead."); + } + // 1x1 transparent PNG so attachment tests don't require an externally supplied image. + byte[] onePixelPng = Base64.getDecoder().decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="); + Path tempFile = Files.createTempFile("vansah-test-screenshot", ".png"); + Files.write(tempFile, onePixelPng); + tempFile.toFile().deleteOnExit(); + return tempFile.toFile(); + } + + @BeforeEach + void setUp() { + assumeTrue(Env.isSet("VANSAH_TOKEN"), + "Skipping: VANSAH_TOKEN not set. Copy .env.example to .env and fill in real Vansah credentials."); + + node = new VansahNode(folderPath, jiraIssueKey); + VansahNode.setVansahURL(vansahUrl); + VansahNode.setVansahToken(token); + VansahNode.setDebug(Env.getBoolean("VANSAH_DEBUG", false)); + // Reset the shared Test Plan iteration to its default (1) so a value set by an + // earlier test cannot leak into a later one (the field is process-global). + node.setTestPlanIteration(1); + if (projectKey != null) { + VansahNode.setProjectKey(projectKey); + } + if (atpKey != null) { + node.setAdvancedTestPlanKey(atpKey); + } + if (stpKey != null) { + node.setStandardTestPlanKey(stpKey); + } + node.setFOLDERPATH(folderPath); + node.setJIRA_ISSUE_KEY(jiraIssueKey); + node.setSPRINT_NAME(sprintName); + node.setRELEASE_NAME(releaseName); + node.setENVIRONMENT_NAME(environmentName); + } + + // ---- Config / validation methods (no network call) ---- + + @Test + void isValidFolderPath_acceptsWellFormedPath() { + assertTrue(node.isValidFolderPath("regression/2025/smoke")); + } + + @Test + void isValidFolderPath_rejectsLeadingSlash() { + assertFalse(node.isValidFolderPath("/regression/2025")); + } + + @Test + void isValidFolderPath_rejectsMissingSeparator() { + assertFalse(node.isValidFolderPath("regression")); + } + + @Test + void isValidFolderPath_rejectsNullOrEmpty() { + assertFalse(node.isValidFolderPath(null)); + assertFalse(node.isValidFolderPath("")); + } + + @Test + void noArgConstructor_initialisesWithoutThrowing() { + VansahNode plain = assertDoesNotThrow(() -> new VansahNode()); + // The no-arg constructor leaves folder/issue unset; validation helpers must still work. + assertFalse(plain.isValidFolderPath(null)); + assertTrue(plain.isValidFolderPath("regression/2025/smoke")); + } + + @Test + void setDebug_togglesWithoutThrowing() { + assertDoesNotThrow(() -> VansahNode.setDebug(true)); + assertDoesNotThrow(() -> VansahNode.setDebug(false)); + } + + @Test + void setVansahURL_resetsToDefaultOnBlankInput() { + assertDoesNotThrow(() -> VansahNode.setVansahURL("")); + VansahNode.setVansahURL(vansahUrl); // restore for any tests sharing the JVM + } + + @Test + void setVansahToken_acceptsArbitraryValue() { + assertDoesNotThrow(() -> VansahNode.setVansahToken("arbitrary-non-empty-token")); + // real token is restored by setUp() before every subsequent test + } + + @Test + void setProjectKey_warnsButDoesNotThrowOnBlankInput() { + assertDoesNotThrow(() -> VansahNode.setProjectKey("")); + if (projectKey != null) { + VansahNode.setProjectKey(projectKey); // restore + } + } + + @Test + void setTestPlanIteration_ignoresOutOfRangeButAcceptsValid() { + // Out-of-range values are ignored with a warning (default of 1 kept); valid ones are accepted. + assertDoesNotThrow(() -> node.setTestPlanIteration(0)); + assertDoesNotThrow(() -> node.setTestPlanIteration(6)); + assertDoesNotThrow(() -> node.setTestPlanIteration(2)); + } + + // ---- JIRA issue lifecycle: run -> log (all 4 addTestLog overloads) -> update (all 4 overloads) -> remove ---- + + @Test + void jiraIssueLifecycle_addLogUpdateRemove() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_JIRA_ISSUE_KEY"), "Skipping: VANSAH_JIRA_ISSUE_KEY not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + + assertDoesNotThrow(() -> node.addTestRunFromJIRAIssue(testCaseKey)); + + // Runs are created UNTESTED with one pre-created log per step; step 1 always exists. + final int step = 1; + + assertDoesNotThrow(() -> node.addTestLog(2, "Automated: addTestLog(int, comment, step)", step)); + assertDoesNotThrow(() -> node.addTestLog("passed", "Automated: addTestLog(String, comment, step)", step)); + assertDoesNotThrow(() -> node.addTestLog(2, "Automated: addTestLog(int, comment, step, image)", step, screenshotFile)); + assertDoesNotThrow(() -> node.addTestLog("passed", "Automated: addTestLog(String, comment, step, image)", step, screenshotFile)); + + assertDoesNotThrow(() -> node.updateTestLog(1, "Automated: updateTestLog(int, comment)")); + assertDoesNotThrow(() -> node.updateTestLog("failed", "Automated: updateTestLog(String, comment)")); + assertDoesNotThrow(() -> node.updateTestLog(1, "Automated: updateTestLog(int, comment, image)", screenshotFile)); + assertDoesNotThrow(() -> node.updateTestLog("failed", "Automated: updateTestLog(String, comment, image)", screenshotFile)); + + assertDoesNotThrow(() -> node.removeTestLog()); + assertDoesNotThrow(() -> node.removeTestRun()); + } + + // ---- Test folder lifecycle ---- + + @Test + void testFolderLifecycle_addLogRemove() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_FOLDER_PATH"), "Skipping: VANSAH_FOLDER_PATH not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + + assertDoesNotThrow(() -> node.addTestRunFromTestFolder(testCaseKey)); + assertDoesNotThrow(() -> node.addTestLog("passed", "Automated: test folder log", 1)); + assertDoesNotThrow(() -> node.removeTestLog()); + assertDoesNotThrow(() -> node.removeTestRun()); + } + + // ---- Attachments (screenshot upload) ---- + + @Test + void attachmentLifecycle_addLogWithImage_thenUpdateWithImage() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_JIRA_ISSUE_KEY"), "Skipping: VANSAH_JIRA_ISSUE_KEY not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + assumeTrue(screenshotFile != null && screenshotFile.isFile(), + "Skipping: no usable screenshot file (generated placeholder failed)."); + + assertDoesNotThrow(() -> node.addTestRunFromJIRAIssue(testCaseKey)); + + // addTestLog(result, comment, step, image) -- uploads the base64 screenshot as an attachment. + assertDoesNotThrow(() -> node.addTestLog("passed", "Automated: log with screenshot attachment", 1, screenshotFile)); + + // updateTestLog(result, comment, image) -- updates the same log with an attachment. + assertDoesNotThrow(() -> node.updateTestLog("failed", "Automated: updated log with screenshot attachment", screenshotFile)); + + assertDoesNotThrow(() -> node.removeTestLog()); + assertDoesNotThrow(() -> node.removeTestRun()); + } + + // ---- Advanced Test Plan ---- + + @Test + void advancedTestPlanLifecycle_addLogRemove() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_ATP_KEY"), "Skipping: VANSAH_ATP_KEY not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + + assertDoesNotThrow(() -> node.addTestRunFromAdvancedTestPlan(atpAssetType, testCaseKey)); + assertDoesNotThrow(() -> node.addTestLog("passed", "Automated: ATP log", 1)); + assertDoesNotThrow(() -> node.removeTestLog()); + assertDoesNotThrow(() -> node.removeTestRun()); + } + + // ---- Standard Test Plan ---- + + @Test + void standardTestPlanLifecycle_addLogRemove() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_STP_KEY"), "Skipping: VANSAH_STP_KEY not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + + assertDoesNotThrow(() -> node.addTestRunFromStandardTestPlan(testCaseKey)); + assertDoesNotThrow(() -> node.addTestLog("passed", "Automated: STP log", 1)); + assertDoesNotThrow(() -> node.removeTestLog()); + assertDoesNotThrow(() -> node.removeTestRun()); + } + + // ---- Quick Test (single overall result, no steps) ---- + + @Test + void quickTestFromJiraIssue() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_JIRA_ISSUE_KEY"), "Skipping: VANSAH_JIRA_ISSUE_KEY not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + + assertDoesNotThrow(() -> node.addQuickTestFromJiraIssue(testCaseKey, 2)); + } + + @Test + void quickTestFromTestFolders() throws Exception { + assumeTrue(Env.isSet("VANSAH_PROJECT_KEY"), "Skipping: VANSAH_PROJECT_KEY not set."); + assumeTrue(Env.isSet("VANSAH_FOLDER_PATH"), "Skipping: VANSAH_FOLDER_PATH not set."); + assumeTrue(Env.isSet("VANSAH_TEST_CASE_KEY"), "Skipping: VANSAH_TEST_CASE_KEY not set."); + + assertDoesNotThrow(() -> node.addQuickTestFromTestFolders(testCaseKey, 2)); + } + + /** + * Tiny config reader: loads a .env file at the project root (KEY=VALUE per line, + * '#' comments and blank lines ignored) and falls back to real OS environment + * variables so the same tests work unmodified in CI. + */ + private static final class Env { + private static final Map VALUES = load(); + + private Env() { + } + + private static Map load() { + Map values = new HashMap<>(); + Path envFile = Paths.get(".env"); + if (Files.isReadable(envFile)) { + try { + for (String line : Files.readAllLines(envFile)) { + String trimmed = line.trim(); + if (trimmed.isEmpty() || trimmed.startsWith("#")) { + continue; + } + int eq = trimmed.indexOf('='); + if (eq <= 0) { + continue; + } + String key = trimmed.substring(0, eq).trim(); + String value = trimmed.substring(eq + 1).trim(); + if (value.length() >= 2 + && ((value.startsWith("\"") && value.endsWith("\"")) + || (value.startsWith("'") && value.endsWith("'")))) { + value = value.substring(1, value.length() - 1); + } + values.put(key, value); + } + } catch (IOException e) { + System.out.println("⚠️ Warning: Failed to read .env file: " + e.getMessage()); + } + } + return values; + } + + static String get(String key) { + return get(key, null); + } + + static String get(String key, String defaultValue) { + String value = VALUES.get(key); + if (value == null || value.isEmpty()) { + value = System.getenv(key); + } + if (value == null || value.isEmpty()) { + value = defaultValue; + } + return value; + } + + static boolean getBoolean(String key, boolean defaultValue) { + String value = get(key); + if (value == null) { + return defaultValue; + } + return "true".equalsIgnoreCase(value) || "1".equals(value); + } + + static boolean isSet(String key) { + String value = get(key); + return value != null && !value.trim().isEmpty(); + } + } +} diff --git a/src/test/java/com/vansah/VansahNodeTests.java b/src/test/java/com/vansah/VansahNodeTests.java deleted file mode 100644 index 2761a9d..0000000 --- a/src/test/java/com/vansah/VansahNodeTests.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.vansah; - -public class VansahNodeTests { - - //Optional if IssueKey is provided - private static String testFolderID = "1ba1372f-54ed-11ed-8e52-5658ef8eadd5"; //TestFolder ID to which test Execution is to be perform - - //Optional if TestFolder ID is provided - private static String issueKey = "VAN-1"; //IssueKey to which test Execution is to be perform - - //Optional - private static String sprintName = "VAN Sprint 1"; //Sprint Name for current sprint for which test execution is to be perform - - //Optional - private static String releaseName = "v1"; //Release Name linked with the current sprint and to the test case. - - //Optional - private static String environment = "SYS"; //Environment Name to which test execution of a test case is to be perform - - //Required - private static String testCase = "VAN-C3"; - - public static void main(String[] args) throws Exception { - - - //Provide TestFolder ID , JIRA Issue, Sprint Key, Sprint Release and Environment - VansahNode testExecute = new VansahNode(testFolderID,issueKey); - - //From Jira Issue Screen - testExecute.addTestRunFromJIRAIssue(testCase); - - for(int i = 1;i<=testExecute.testStepCount(testCase);i++) { - - //Add logs for each step function(ResultID, AcutalResultComment, TestStepID, screenshot file); - testExecute.addTestLog(2, "This is From Java Binder Add test log", i, null); - - //Will update the current test log - testExecute.updateTestLog(1, "This is From Java Binder Update Test log", null); - - //To remove the current test log - testExecute.removeTestLog(); - - - } - - testExecute.removeTestRun(); //Removing current test run - - //From Test folder screen - testExecute.addTestRunFromTestFolder(testCase); - - for(int i = 1;i<=testExecute.testStepCount(testCase);i++) { - - //Add logs for each step function(ResultID, AcutalResultComment, TestStepID, screenshot file); - - testExecute.addTestLog(2, "This is From Java Binder Add test log", i, null); - - //Will update the current test log - testExecute.updateTestLog(1, "This is From Java Binder Update Test log", null); - - //To remove the current test log - //testExecute.remove_test_log(); - - } - - //Add Quick test for Jira issue function(testCaseKey, ResultID, AcutalResultComment, screenshot file); - - testExecute.addQuickTestFromJiraIssue(testCase, 0); - - //Add Quick test for Test folders - testExecute.addQuickTestFromTestFolders(testCase, 0); - } - - -} -