Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions .github/ISSUE_TEMPLATE/mosip-bug-report.md

This file was deleted.

20 changes: 0 additions & 20 deletions .github/ISSUE_TEMPLATE/mosip-feature-or-enhancement-request.md

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ test.txt
/.recommenders/
**/*.iml
.vscode
.claude/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![Maven Package upon a push](https://github.com/mosip/resident-services/actions/workflows/push-trigger.yml/badge.svg?branch=release-1.3.x)](https://github.com/mosip/resident-services/actions/workflows/push-trigger.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mosip_resident-services&id=mosip_resident-services&branch=release-1.3.x&metric=alert_status)](https://sonarcloud.io/dashboard?id=mosip_resident-services&branch=release-1.3.0)
[![Maven Package upon a push](https://github.com/mosip/resident-services/actions/workflows/push-trigger.yml/badge.svg?branch=master)](https://github.com/mosip/resident-services/actions/workflows/push-trigger.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mosip_resident-services&id=mosip_resident-services&branch=master)](https://sonarcloud.io/dashboard?id=mosip_resident-services&branch=master)

# MOSIP Resident Services

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,35 @@ public void test(TestCaseDTO testCaseDTO) throws AuthenticationTestException, Ad
GlobalConstants.RESIDENT, testCaseDTO.getTestCaseName());
}

if (otpResponse != null && (otpResponse.asString().contains("RES-SER-524")
|| otpResponse.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 (otpResponse == null) {
logger.error("Received null otpResponse while invoking send-otp endpoint; aborting retry loop.");
break;
}

String otpResponseBody = otpResponse.asString();
if (otpResponseBody.contains("RES-SER-524")) {
int discarded = ResidentUtil.discardCancellablePendingDrafts(
testCaseDTO.getRole(), testCaseDTO.getTestCaseName());
logger.info("RES-SER-524 on send-otp: discarded " + discarded
+ " cancellable draft(s) before retrying.");
if (discarded == 0) {
sleepForUinGenDelay();
}
} else if (otpResponseBody.contains("RES-SER-525")) {
logger.info("RES-SER-525 on send-otp: previous packet is non-cancellable, waiting "
+ properties.getProperty("uinGenDelayTime") + " ms before retry.");
sleepForUinGenDelay();
} else {
break;
}

currLoopCount++;
}

if (otpResponse == null) {
throw new AdminTestException("Received null otpResponse from send-otp endpoint; cannot validate output.");
}

JSONObject res = new JSONObject(testCaseDTO.getOutput());
String sendOtpResp = null, sendOtpResTemplate = null;
if (res.has(GlobalConstants.SENDOTPRESP)) {
Expand Down Expand Up @@ -169,15 +180,50 @@ public void test(TestCaseDTO testCaseDTO) throws AuthenticationTestException, Ad
logger.info("waiting for " + properties.getProperty("expireOtpTime")
+ " mili secs to test expire otp case in RESIDENT Service");
Thread.sleep(Long.parseLong(properties.getProperty("expireOtpTime")));
} catch (NumberFormatException | InterruptedException e) {
} catch (NumberFormatException e) {
logger.error(e.getMessage());
} catch (InterruptedException e) {
logger.error(e.getMessage());
Thread.currentThread().interrupt();
}
}

response = postRequestWithCookieAndHeader(ApplnURI + testCaseDTO.getEndPoint(),
getJsonFromTemplate(req.toString(), testCaseDTO.getInputTemplate()), COOKIENAME, testCaseDTO.getRole(),
testCaseDTO.getTestCaseName(), sendEsignetToken);
// The actual update-data POST is what triggers the "previous packet pending"
// check inside ValidateNewUpdateRequest, so retry it (and discard cancellable
// drafts on RES-SER-524) the same way we do for the send-otp call above.
int updateLoopCount = 0;
while (updateLoopCount < maxLoopCount) {
response = postRequestWithCookieAndHeader(ApplnURI + testCaseDTO.getEndPoint(),
getJsonFromTemplate(req.toString(), testCaseDTO.getInputTemplate()), COOKIENAME,
testCaseDTO.getRole(), testCaseDTO.getTestCaseName(), sendEsignetToken);

if (response == null) {
logger.error("Received null response while invoking update-data 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 on update-data: discarded " + discarded
+ " cancellable draft(s) before retrying.");
if (discarded == 0) {
sleepForUinGenDelay();
}
} else if (responseBody.contains("RES-SER-525")) {
logger.info("RES-SER-525 on update-data: previous packet is non-cancellable, waiting "
+ properties.getProperty("uinGenDelayTime") + " ms before retry.");
sleepForUinGenDelay();
} else {
break;
}
updateLoopCount++;
}

if (response == null) {
throw new AdminTestException("Received null response from update-data endpoint; cannot validate output.");
}
Map<String, List<OutputValidationDto>> ouputValid = OutputValidationUtil.doJsonOutputValidation(
response.asString(), getJsonFromTemplate(res.toString(), testCaseDTO.getOutputTemplate()), testCaseDTO,
response.getStatusCode());
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,24 +134,36 @@ 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;
}

currLoopCount++;
}

if (response == null) {
throw new AdminTestException("Received null response from endpoint; cannot validate output.");
}

Map<String, List<OutputValidationDto>> ouputValid = null;
if (testCaseName.contains("_StatusCode")) {

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down