diff --git a/.github/ISSUE_TEMPLATE/mosip-bug-report.md b/.github/ISSUE_TEMPLATE/mosip-bug-report.md deleted file mode 100644 index d73e9e7a618..00000000000 --- a/.github/ISSUE_TEMPLATE/mosip-bug-report.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: MOSIP Bug report -about: Create a report to help us improve -title: "[BUG] > ouputValid = OutputValidationUtil.doJsonOutputValidation( response.asString(), getJsonFromTemplate(res.toString(), testCaseDTO.getOutputTemplate()), testCaseDTO, response.getStatusCode()); @@ -193,6 +239,17 @@ public void test(TestCaseDTO testCaseDTO) throws AuthenticationTestException, Ad * * @param result */ + private void sleepForUinGenDelay() { + try { + Thread.sleep(Long.parseLong(properties.getProperty("uinGenDelayTime"))); + } catch (NumberFormatException e) { + logger.error(e.getMessage()); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + Thread.currentThread().interrupt(); + } + } + @AfterMethod(alwaysRun = true) public void setResultTestName(ITestResult result) { try { diff --git a/api-test/src/main/java/io/mosip/testrig/apirig/resident/testscripts/SimplePost.java b/api-test/src/main/java/io/mosip/testrig/apirig/resident/testscripts/SimplePost.java index 37859add9f0..bc76bb97d8f 100644 --- a/api-test/src/main/java/io/mosip/testrig/apirig/resident/testscripts/SimplePost.java +++ b/api-test/src/main/java/io/mosip/testrig/apirig/resident/testscripts/SimplePost.java @@ -134,17 +134,25 @@ public void test(TestCaseDTO testCaseDTO) throws AuthenticationTestException, Ad response = postWithBodyAndCookie(ApplnURI + testCaseDTO.getEndPoint(), inputJson, auditLogCheck, COOKIENAME, testCaseDTO.getRole(), testCaseDTO.getTestCaseName(), sendEsignetToken); - if (response != null && (response.asString().contains("RES-SER-524") - || response.asString().contains("RES-SER-525"))) { - logger.info("waiting for: " + properties.getProperty("uinGenDelayTime") - + " to update UIN as previous packet is pending."); - try { - Thread.sleep(Long.parseLong(properties.getProperty("uinGenDelayTime"))); - - } catch (NumberFormatException | InterruptedException e) { - logger.error(e.getMessage()); - Thread.currentThread().interrupt(); + if (response == null) { + logger.error("Received null response while invoking endpoint; aborting retry loop."); + break; + } + + String responseBody = response.asString(); + if (responseBody.contains("RES-SER-524")) { + int discarded = ResidentUtil.discardCancellablePendingDrafts( + testCaseDTO.getRole(), testCaseDTO.getTestCaseName()); + logger.info("RES-SER-524: discarded " + discarded + + " cancellable draft(s) before retrying."); + if (discarded == 0) { + // Nothing was actually cancellable right now — fall back to wait. + sleepForUinGenDelay(); } + } else if (responseBody.contains("RES-SER-525")) { + logger.info("RES-SER-525: previous packet is non-cancellable, waiting " + + properties.getProperty("uinGenDelayTime") + " ms before retry."); + sleepForUinGenDelay(); } else { break; } @@ -152,6 +160,10 @@ public void test(TestCaseDTO testCaseDTO) throws AuthenticationTestException, Ad currLoopCount++; } + if (response == null) { + throw new AdminTestException("Received null response from endpoint; cannot validate output."); + } + Map> ouputValid = null; if (testCaseName.contains("_StatusCode")) { @@ -179,9 +191,20 @@ public void test(TestCaseDTO testCaseDTO) throws AuthenticationTestException, Ad } + private void sleepForUinGenDelay() { + try { + Thread.sleep(Long.parseLong(properties.getProperty("uinGenDelayTime"))); + } catch (NumberFormatException e) { + logger.error(e.getMessage()); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + Thread.currentThread().interrupt(); + } + } + /** * The method ser current test name to result - * + * * @param result */ @AfterMethod(alwaysRun = true) diff --git a/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentConfigManager.java b/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentConfigManager.java index 54300cf4cf2..4fe85a605db 100644 --- a/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentConfigManager.java +++ b/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentConfigManager.java @@ -22,9 +22,22 @@ public static void init() { try { String path = MosipTestRunner.getGlobalResourcePath() + "/config/resident.properties"; Properties props = getproperties(path); - // Convert Properties to Map and add to moduleSpecificPropertiesMap + // Convert Properties to Map and add to moduleSpecificPropertiesMap. + // When a value is blank in the file, fall back to an environment variable + // so secrets (client secrets, keycloak passwords, db passwords, ...) can be + // supplied at runtime without committing them. for (String key : props.stringPropertyNames()) { - moduleSpecificPropertiesMap.put(key, props.getProperty(key)); + String value = props.getProperty(key); + if (value == null || value.trim().isEmpty()) { + String envValue = resolveFromEnv(key); + if (envValue != null) { + value = envValue; + LOGGER.info("Resolved blank property '" + key + "' from environment."); + } + } + // Never store null — downstream callers cast/use these as String + // and would NPE. Preserve the original blank if nothing resolved. + moduleSpecificPropertiesMap.put(key, (value == null) ? "" : value); } } catch (Exception e) { LOGGER.error(e.getMessage()); @@ -33,6 +46,25 @@ public static void init() { init(moduleSpecificPropertiesMap); } - + /** + * Looks up the value for the given property key in process environment. + * Tries the key as-is first, then an UPPER_SNAKE_CASE form (dots, dashes + * and slashes converted to underscores) for the typical CI convention. + * Returns null when neither variant is set or both are empty. + */ + private static String resolveFromEnv(String key) { + String value = System.getenv(key); + if (value != null && !value.trim().isEmpty()) { + return value; + } + String upperKey = key.replaceAll("[\\.\\-/]", "_").toUpperCase(); + if (!upperKey.equals(key)) { + value = System.getenv(upperKey); + if (value != null && !value.trim().isEmpty()) { + return value; + } + } + return null; + } } \ No newline at end of file diff --git a/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentUtil.java b/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentUtil.java index 55aaedb2ae1..f28e1f78ad8 100644 --- a/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentUtil.java +++ b/api-test/src/main/java/io/mosip/testrig/apirig/resident/utils/ResidentUtil.java @@ -17,6 +17,7 @@ import io.mosip.testrig.apirig.utils.AdminTestUtil; import io.mosip.testrig.apirig.utils.GlobalConstants; import io.mosip.testrig.apirig.utils.GlobalMethods; +import io.mosip.testrig.apirig.utils.KernelAuthentication; import io.mosip.testrig.apirig.utils.RestClient; import io.mosip.testrig.apirig.utils.SkipTestCaseHandler; import io.restassured.response.Response; @@ -181,6 +182,68 @@ public static String getValueFromEsignetActuator(String section, String key) { } + /** + * Best-effort cleanup that fetches the resident's pending drafts and discards + * the cancellable ones for the supplied role. Used by retry loops that hit + * RES-SER-524 ("Not allowed to update UIN as previous packet is pending. To + * proceed further please discard it.") so the next retry has a clean slate + * instead of waiting for an old cancellable draft to time out on its own. + * + * Returns the number of drafts that were successfully discarded. Never throws. + */ + public static int discardCancellablePendingDrafts(String role, String testCaseName) { + try { + String token = new KernelAuthentication().getTokenByRole(role); + Response getResp = RestClient.getRequestWithCookie( + ApplnURI + "/resident/v1/identity/get-pending-drafts/eng", + javax.ws.rs.core.MediaType.APPLICATION_JSON, + javax.ws.rs.core.MediaType.APPLICATION_JSON, + COOKIENAME, token); + if (getResp == null || getResp.asString() == null || getResp.asString().isEmpty()) { + return 0; + } + JSONObject body = new JSONObject(getResp.asString()); + JSONObject responseNode = body.optJSONObject("response"); + if (responseNode == null) { + return 0; + } + JSONArray drafts = responseNode.optJSONArray("drafts"); + if (drafts == null || drafts.length() == 0) { + return 0; + } + int discarded = 0; + for (int i = 0; i < drafts.length(); i++) { + JSONObject draft = drafts.optJSONObject(i); + if (draft == null || !draft.optBoolean("cancellable", false)) { + continue; + } + String eid = draft.optString("eid", ""); + if (eid.isEmpty()) { + continue; + } + Response postResp = postWithBodyAndCookie( + ApplnURI + "/resident/v1/identity/discardPendingDraft/" + eid, + "{}", COOKIENAME, role, testCaseName); + if (postResp != null && postResp.asString() != null + && postResp.asString().contains("DISCARDED")) { + discarded++; + logger.info("Discarded a cancellable pending draft."); + } else { + // Do not log eid or full response body — they can contain + // sensitive resident identifiers. Log only the HTTP status. + String statusInfo = (postResp == null) + ? "no-response" + : ("status=" + postResp.getStatusCode()); + logger.warn("Discard request did not return DISCARDED (" + statusInfo + ")."); + } + } + return discarded; + } catch (Exception e) { + logger.error("Failed to discard cancellable pending drafts: " + e.getMessage()); + return 0; + } + } + public static JSONArray configActuatorResponseArray = null; public static String getValueFromConfigActuator() {