From 5fb632880d9db9020ef10184e58be5a80a5129bd Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Wed, 14 Aug 2024 17:46:15 +0530 Subject: [PATCH 01/43] MOSIP-35227 Signed-off-by: Nandhukumar --- .../testrig/apirig/utils/AdminTestUtil.java | 40 ++++++++++--------- .../testrig/apirig/utils/ConfigManager.java | 12 ++++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index bafba170be..d8e3de5295 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -2963,9 +2963,10 @@ public String uriKeyWordHandelerUri(String uri, String testCaseName) { } public static String getAuthTransactionId(String oidcTransactionId) { - final String transactionId = oidcTransactionId.replaceAll("_|-", ""); - String lengthOfTransactionId = AdminTestUtil.getValueFromEsignetActuator("/mosip/mosip-config/esignet-default.properties", "mosip.esignet.auth-txn-id-length"); - int authTransactionIdLength = lengthOfTransactionId != null ? Integer.parseInt(lengthOfTransactionId): 0; + final String transactionId = oidcTransactionId.replaceAll("_|-", ""); + String lengthOfTransactionId = AdminTestUtil.getValueFromEsignetActuator( + ConfigManager.getEsignetActuatorPropertySection(), "mosip.esignet.auth-txn-id-length"); + int authTransactionIdLength = lengthOfTransactionId != null ? Integer.parseInt(lengthOfTransactionId): 0; final byte[] oidcTransactionIdBytes = transactionId.getBytes(); final byte[] authTransactionIdBytes = new byte[authTransactionIdLength]; int i = oidcTransactionIdBytes.length - 1; @@ -3617,8 +3618,9 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { "mosip.iam.module.clientID"); String esignetBaseURI = getValueFromActuator(GlobalConstants.RESIDENT_DEFAULT_PROPERTIES, "mosip.iam.token_endpoint"); - int idTokenExpirySecs = Integer.parseInt(getValueFromEsignetActuator( - GlobalConstants.ESIGNET_DEFAULT_PROPERTIES, GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); + int idTokenExpirySecs = Integer + .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), + GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); Instant instant = Instant.now(); @@ -3816,8 +3818,9 @@ public static String signJWKForMock(String clientId, String accessToken, RSAKey if (tempUrl.contains("esignet.")) { tempUrl = tempUrl.replace("esignet.", propsKernel.getProperty("esignetMockBaseURL")); } - int idTokenExpirySecs = Integer.parseInt(getValueFromEsignetActuator(GlobalConstants.ESIGNET_DEFAULT_PROPERTIES, - GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); + int idTokenExpirySecs = Integer + .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), + GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); JWSSigner signer; String proofJWT = ""; String typ = "openid4vci-proof+jwt"; @@ -3860,10 +3863,10 @@ public static String signJWKForMock(String clientId, String accessToken, RSAKey } public static String signJWK(String clientId, String accessToken, RSAKey jwkKey, String testCaseName) { -// String tempUrl = getValueFromActuator(GlobalConstants.RESIDENT_DEFAULT_PROPERTIES, "mosip.iam.base.url"); String tempUrl = getValueFromEsignetWellKnownEndPoint("issuer"); - int idTokenExpirySecs = Integer.parseInt(getValueFromEsignetActuator(GlobalConstants.ESIGNET_DEFAULT_PROPERTIES, - GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); + int idTokenExpirySecs = Integer + .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), + GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); JWSSigner signer; String proofJWT = ""; String typ = "openid4vci-proof+jwt"; @@ -5951,13 +5954,13 @@ public static void createAndPublishPolicyForKyc() { // } public static String signJWKKeyForMock(String clientId, RSAKey jwkKey) { -// String tempUrl = getValueFromActuator(GlobalConstants.RESIDENT_DEFAULT_PROPERTIES, "mosip.iam.token_endpoint"); String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint"); if (tempUrl.contains("esignet.")) { tempUrl = tempUrl.replace("esignet.", propsKernel.getProperty("esignetMockBaseURL")); } - int idTokenExpirySecs = Integer.parseInt(getValueFromEsignetActuator(GlobalConstants.ESIGNET_DEFAULT_PROPERTIES, - GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); + int idTokenExpirySecs = Integer + .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), + GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); JWSSigner signer; try { @@ -5992,10 +5995,10 @@ public static String signJWKKeyForMock(String clientId, RSAKey jwkKey) { } public static String signJWKKey(String clientId, RSAKey jwkKey) { -// String tempUrl = getValueFromActuator(GlobalConstants.RESIDENT_DEFAULT_PROPERTIES, "mosip.iam.token_endpoint"); String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint"); - int idTokenExpirySecs = Integer.parseInt(getValueFromEsignetActuator(GlobalConstants.ESIGNET_DEFAULT_PROPERTIES, - GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); + int idTokenExpirySecs = Integer + .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), + GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); JWSSigner signer; try { @@ -6032,8 +6035,9 @@ public static String signJWKKey(String clientId, RSAKey jwkKey) { public static String getWlaToken(String individualId, RSAKey jwkKey, String certData) throws JoseException, JOSEException { String tempUrl = propsKernel.getProperty("validateBindingEndpoint"); - int idTokenExpirySecs = Integer.parseInt(getValueFromEsignetActuator(GlobalConstants.ESIGNET_DEFAULT_PROPERTIES, - GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); + int idTokenExpirySecs = Integer + .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), + GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); Instant instant = Instant.now(); long epochValue = instant.getEpochSecond(); diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java index ce3d0266bc..97feda8648 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java @@ -75,6 +75,7 @@ public class ConfigManager { private static String USEPRECONFIGOTP = "usePreConfiguredOtp"; private static String ESIGNET_BASE_URL = "eSignetbaseurl"; + private static String ESIGNET_ACTUATOR_PROPERTY_SECTION = "esignetActuatorPropertySection"; private static String ESIGNET_MOCK_BASE_URL = "esignetMockBaseURL"; private static String SUNBIRD_BASE_URL = "sunBirdBaseURL"; @@ -205,6 +206,7 @@ public class ConfigManager { private static String eSignetbaseurl; private static String esignetMockBaseURL; private static String sunBirdBaseURL; + private static String esignetActuatorPropertySection; private static String dbPort; private static String dbDomain; @@ -406,6 +408,11 @@ public static void init() { ? propsKernel.getProperty(USE_EXTERNAL_SCENARIO_SHEET) : System.getenv(USE_EXTERNAL_SCENARIO_SHEET); propsKernel.setProperty(USE_EXTERNAL_SCENARIO_SHEET, useExternalScenario_sheet); + + esignetActuatorPropertySection = System.getenv(ESIGNET_ACTUATOR_PROPERTY_SECTION) == null + ? propsKernel.getProperty(ESIGNET_ACTUATOR_PROPERTY_SECTION) + : System.getenv(ESIGNET_ACTUATOR_PROPERTY_SECTION); + propsKernel.setProperty(ESIGNET_ACTUATOR_PROPERTY_SECTION, esignetActuatorPropertySection); if (System.getenv(ESIGNET_BASE_URL) != null) { eSignetbaseurl = System.getenv(ESIGNET_BASE_URL); @@ -519,6 +526,11 @@ public static boolean isInTobeExecuteList(String stringToFind) { return false; } + public static String getEsignetActuatorPropertySection() { + return esignetActuatorPropertySection; + + } + public static String getComponentBaseURL(String component) { return mosip_components_base_urls.get(component); } From 64a2e2e672a6911ca10abe9432fb82304a62dab5 Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:37:06 +0530 Subject: [PATCH 02/43] array handling related code Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 16 ++++++++++++++-- .../apirig/utils/KernelAuthentication.java | 19 ++----------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index c05876cb32..8d60e303fa 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -5003,6 +5003,7 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { JSONObject identityJson = new JSONObject(); identityJson.put("UIN", "{{UIN}}"); + List selectedHandles = new ArrayList<>(); //requiredPropsArray.put("functionalId"); for (int i = 0, size = requiredPropsArray.length(); i < size; i++) { String eachRequiredProp = requiredPropsArray.getString(i); @@ -5027,15 +5028,24 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { JSONObject eachValueJsonForHandles = new JSONObject(); if (eachRequiredProp.equals(emailResult)) { eachValueJsonForHandles.put("value", "$EMAILVALUE$"); + eachValueJsonForHandles.put("tags", ":[handle]"); + selectedHandles.add(emailResult); + } else if (eachRequiredProp.equals(result)) { eachValueJsonForHandles.put("value", "$PHONENUMBERFORIDENTITY$"); + eachValueJsonForHandles.put("tags", ":[handle]"); + selectedHandles.add(result); } else { eachValueJsonForHandles.put("value", "1726266"); + eachValueJsonForHandles.put("tags", ":[handle]"); + selectedHandles.add(eachRequiredProp); } eachPropDataArrayForHandles.put(eachValueJsonForHandles); - identityJson.put(eachRequiredProp, eachPropDataArrayForHandles); + identityJson.put(eachRequiredProp, eachPropDataArrayForHandles); + } - + + else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString().contains("simpleType")) { JSONArray eachPropDataArray = new JSONArray(); @@ -5060,6 +5070,7 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() } } identityJson.put(eachRequiredProp, eachPropDataArray); + } else { if (eachRequiredProp.equals("proofOfIdentity")) { identityJson.put(eachRequiredProp, new HashMap<>()); @@ -5096,6 +5107,7 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() } } } + identityJson.put("selectedHandles", selectedHandles); // Constructing and adding functionalIds JSONArray functionalIdsArray = new JSONArray(); diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java index bf40ce6a12..2537758499 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java @@ -159,7 +159,7 @@ public String getTokenByRole(String role, String tokenType) { return batchJobToken; case "invalidBatch": - if (!kernelCmnLib.isValidToken(batchJobToken)) + if (!kernelCmnLib.isValidToken(invalidBatchJobToken)) invalidBatchJobToken = kernelAuthLib.getPreRegInvalidToken(); return invalidBatchJobToken; @@ -556,22 +556,7 @@ public String getPreRegToken() { @SuppressWarnings("unchecked") public String getPreRegInvalidToken() { - JSONObject actualRequest_generation = getRequestJson("config/prereg_SendOtp.json"); - actualRequest_generation.put(GlobalConstants.REQUESTTIME, clib.getCurrentUTCTime()); - ((JSONObject) actualRequest_generation.get(GlobalConstants.REQUEST)).put("langCode", - BaseTestCase.getLanguageList().get(0)); - ((JSONObject) actualRequest_generation.get(GlobalConstants.REQUEST)).get("userId").toString(); - JSONObject actualRequest_validation = getRequestJson("config/prereg_ValidateOtp.json"); - appl.postWithJson(preregSendOtp, actualRequest_generation); - String otp = null; - if (proxy) - otp = "111222"; - else { - } - ((JSONObject) actualRequest_validation.get(GlobalConstants.REQUEST)).put("otp", otp); - actualRequest_validation.put(GlobalConstants.REQUESTTIME, clib.getCurrentUTCTime()); - Response otpValidate = appl.postWithJson(preregValidateOtp, actualRequest_validation); - cookie = otpValidate.getCookie(GlobalConstants.AUTHORIZATION); + cookie = "ddhdh76478383hdgdgdgg@#$%$%%^^^^^$###$fgdhdhdjj"; return cookie; } From 216c9b0ce525aef4af702b7d8d54b8200cfb1cae Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Fri, 23 Aug 2024 15:18:04 +0530 Subject: [PATCH 03/43] Implementaion for creating location data for primary language along with secondary language Signed-off-by: Mohanachandran S --- .../apirig/testrunner/BaseTestCase.java | 6 ++++++ .../testrig/apirig/utils/AdminTestUtil.java | 20 +++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java index bd0a877622..6657f1a2c7 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java @@ -740,4 +740,10 @@ public static String generateRandomNumberString(int length) { } return numericString.toString(); } + + public static int getRecommendedHierarchyLevel() { + String recommendedHierarchLevel = getValueFromActuators(propsKernel.getProperty("actuatorMasterDataEndpoint"), + "/mosip-config/application-default.properties", "mosip.recommended.centers.locCode"); + return Integer.parseInt(recommendedHierarchLevel); + } } diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index d8e3de5295..c224e8bc88 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -185,6 +185,7 @@ public class AdminTestUtil extends BaseTestCase { public static final String AUTH_HEADER_VALUE = "Some String"; public static final String SIGNATURE_HEADERNAME = GlobalConstants.SIGNATURE; public static String updatedPolicyId = ""; + public static String currentLanguage; // public static BioDataUtility bioDataUtil = new BioDataUtility(); // // public static BioDataUtility getBioDataUtil() { @@ -6965,6 +6966,7 @@ public static void getLocationData() { JSONObject responseJson = null; String url = ApplnURI + props.getProperty("fetchLocationData"); String token = kernelAuthLib.getTokenByRole(GlobalConstants.ADMIN); + int recommendedHierarchyLevel = getRecommendedHierarchyLevel(); try { response = RestClient.getRequestWithCookie(url, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON, GlobalConstants.AUTHORIZATION, token); @@ -6975,15 +6977,21 @@ public static void getLocationData() { JSONObject responseObject = responseJson.getJSONObject("response"); JSONArray data = responseObject.getJSONArray("data"); + if (!(languageList.size() > 1)) { + currentLanguage = BaseTestCase.languageList.get(0); + } + for (int i = 0; i < data.length(); i++) { JSONObject entry = data.getJSONObject(i); String langCode = entry.getString("langCode"); - - if (BaseTestCase.languageList.get(0).equals(langCode)) { - hierarchyName = entry.getString("hierarchyName"); - hierarchyLevel = entry.getInt("hierarchyLevel"); - parentLocCode = entry.optString("parentLocCode", ""); - break; + hierarchyLevel = entry.getInt("hierarchyLevel"); + + if (hierarchyLevel == recommendedHierarchyLevel) { + if (currentLanguage.equals(langCode)) { + hierarchyName = entry.getString("hierarchyName"); + parentLocCode = entry.optString("parentLocCode", ""); + break; + } } } From f56282acd6baa3fe3345e95a5275b4f9e2f4a4ca Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Tue, 27 Aug 2024 10:49:49 +0530 Subject: [PATCH 04/43] MOSIP-35295 Signed-off-by: Nandhukumar --- .../apirig/testrunner/BaseTestCase.java | 18 ++-- .../testrig/apirig/utils/AdminTestUtil.java | 91 +++++++++++++++++-- .../testrig/apirig/utils/ConfigManager.java | 25 +++++ .../apirig/utils/OutputValidationUtil.java | 3 +- 4 files changed, 118 insertions(+), 19 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java index bd0a877622..0ec377e1a8 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/BaseTestCase.java @@ -328,15 +328,15 @@ public static void suiteSetup() { setReportName(GlobalConstants.PREREG); AdminTestUtil.copyPreregTestResource(); } -// if (listOfModules.contains(GlobalConstants.INJICERTIFY)) { -// BaseTestCase.currentModule = GlobalConstants.INJICERTIFY; -// BaseTestCase.certsForModule = GlobalConstants.INJICERTIFY; -// DBManager.clearKeyManagerDbCertData(); -// DBManager.clearIDADbCertData(); -// DBManager.clearMasterDbCertData(); -// setReportName(GlobalConstants.INJICERTIFY); -// AdminTestUtil.copymoduleSpecificAndConfigFile(GlobalConstants.INJICERTIFY); -// } + if (listOfModules.contains(GlobalConstants.INJICERTIFY)) { + BaseTestCase.currentModule = GlobalConstants.INJICERTIFY; + BaseTestCase.certsForModule = GlobalConstants.INJICERTIFY; + DBManager.clearKeyManagerDbCertData(); + DBManager.clearIDADbCertData(); + DBManager.clearMasterDbCertData(); + setReportName(GlobalConstants.INJICERTIFY); + AdminTestUtil.copymoduleSpecificAndConfigFile(GlobalConstants.INJICERTIFY); + } mockSMTPListener = new MockSMTPListener(); mockSMTPListener.run(); } diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index d8e3de5295..c922f29244 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -3393,7 +3393,7 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { } if (jsonString.contains("$GETCLIENTIDFROMMIMOTOACTUATOR$")) { jsonString = replaceKeywordWithValue(jsonString, "$GETCLIENTIDFROMMIMOTOACTUATOR$", - getValueFromMimotoActuator("configService:overrides", "mimoto.oidc.partner.clientid")); + getValueFromMimotoActuator("overrides", "mimoto.oidc.partner.clientid")); } if (jsonString.contains("$IDPREDIRECTURI$")) { jsonString = replaceKeywordWithValue(jsonString, "$IDPREDIRECTURI$", @@ -3794,6 +3794,7 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { JSONObject request = new JSONObject(jsonString); String clientId = ""; String accessToken = ""; + String tempUrl = ""; if (request.has("client_id")) { clientId = request.getString("client_id"); request.remove("client_id"); @@ -3802,8 +3803,21 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { accessToken = request.getString("idpAccessToken"); } jsonString = request.toString(); + + if (BaseTestCase.currentModule.equals(GlobalConstants.INJICERTIFY)) { + String baseURL = ConfigManager.getInjiCertifyBaseUrl(); + if (testCaseName.contains("_GetCredentialSunBirdC")) { + tempUrl = getValueFromInjiCertifyWellKnownEndPoint("credential_issuer", + baseURL.replace("injicertify.", "injicertify-insurance.")); + } + } else { + tempUrl = getValueFromEsignetWellKnownEndPoint("issuer"); + if (tempUrl.contains("esignet.")) { + tempUrl = tempUrl.replace("esignet.", propsKernel.getProperty("esignetMockBaseURL")); + } + } jsonString = replaceKeywordWithValue(jsonString, "$PROOF_JWT_2$", - signJWKForMock(clientId, accessToken, oidcJWKKey4, testCaseName)); + signJWKForMock(clientId, accessToken, oidcJWKKey4, testCaseName, tempUrl)); } if (jsonString.contains(GlobalConstants.REMOVE)) @@ -3812,12 +3826,8 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { return jsonString; } - public static String signJWKForMock(String clientId, String accessToken, RSAKey jwkKey, String testCaseName) { -// String tempUrl = getValueFromActuator(GlobalConstants.RESIDENT_DEFAULT_PROPERTIES, "mosip.iam.base.url"); - String tempUrl = getValueFromEsignetWellKnownEndPoint("issuer"); - if (tempUrl.contains("esignet.")) { - tempUrl = tempUrl.replace("esignet.", propsKernel.getProperty("esignetMockBaseURL")); - } + public static String signJWKForMock(String clientId, String accessToken, RSAKey jwkKey, String testCaseName, + String tempUrl) { int idTokenExpirySecs = Integer .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); @@ -4215,7 +4225,47 @@ private String replaceIdWithAutogeneratedId(String jsonString, String idKey, Str if (!jsonString.contains(idKey)) return jsonString; String keyForIdProperty = StringUtils.substringBetween(jsonString, idKey, "$"); - String keyToReplace = idKey + keyForIdProperty + "$"; + String keyToReplace = ""; + + // mock = email,phone; default + // mock = phone; + // mock = email; + + // $ID:AddIdentity_withValidParameters_smoke_Pos_EMAIL$ + + // $ID:AddIdentity_withValidParameters_smoke_Pos_PHONE$@phone + + + + if (keyForIdProperty.endsWith("_EMAIL") && ConfigManager.getMockNotificationChannel().equalsIgnoreCase("phone")) { + String temp = idKey + keyForIdProperty + "$" ; //$ID:AddIdentity_withValidParameters_smoke_Pos_EMAIL$ + keyForIdProperty = keyForIdProperty.replace("_EMAIL", "_PHONE"); // AddIdentity_withValidParameters_smoke_Pos_PHONE + keyToReplace = temp; // $ID:AddIdentity_withValidParameters_smoke_Pos_PHONE$@phone + + jsonString = jsonString.replace(temp, temp + "@phone"); + + + } else if (keyForIdProperty.endsWith("_PHONE") && ConfigManager.getMockNotificationChannel().equalsIgnoreCase("email")) { + String temp = idKey + keyForIdProperty + "$" ; //$ID:AddIdentity_withValidParameters_smoke_Pos_PHONE$ + keyForIdProperty = keyForIdProperty.replace("_PHONE", "_EMAIL"); // AddIdentity_withValidParameters_smoke_Pos_EMAIL + keyToReplace = temp + "@phone"; + } else { + keyToReplace = idKey + keyForIdProperty + "$"; //AddIdentity_withValidParameters_smoke_Pos_EMAIL + } + + + + + + + + + + + + + + Properties props = new Properties(); try (FileInputStream inputStream = new FileInputStream(getResourcePath() + autoGenIdFileName);) { @@ -6144,6 +6194,29 @@ public static String getValueFromEsignetWellKnownEndPoint(String key) { } return responseJson.getString(key); } + + public static String getValueFromInjiCertifyWellKnownEndPoint(String key, String baseURL) { + String url = baseURL + propsKernel.getProperty("injiCertifyWellKnownEndPoint"); + + String actuatorCacheKey = url + key; + String value = actuatorValueCache.get(actuatorCacheKey); + if (value != null && !value.isEmpty()) + return value; + + Response response = null; + JSONObject responseJson = null; + try { + response = RestClient.getRequest(url, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON); + responseJson = new org.json.JSONObject(response.getBody().asString()); + if (responseJson.has(key)) { + actuatorValueCache.put(actuatorCacheKey, responseJson.getString(key)); + return responseJson.getString(key); + } + } catch (Exception e) { + logger.error(GlobalConstants.EXCEPTION_STRING_2 + e); + } + return responseJson.getString(key); + } public static JSONObject signUpSettingsResponseJson = null; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java index 97feda8648..50d6c2fb0f 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java @@ -76,6 +76,7 @@ public class ConfigManager { private static String USEPRECONFIGOTP = "usePreConfiguredOtp"; private static String ESIGNET_BASE_URL = "eSignetbaseurl"; private static String ESIGNET_ACTUATOR_PROPERTY_SECTION = "esignetActuatorPropertySection"; + private static String INJI_CERTIFY_BASE_URL = "injiCertifyBaseURL"; private static String ESIGNET_MOCK_BASE_URL = "esignetMockBaseURL"; private static String SUNBIRD_BASE_URL = "sunBirdBaseURL"; @@ -122,6 +123,7 @@ public class ConfigManager { private static String MOUNT_PATH = "mountPath"; private static String AUTHCERTS_PATH = "authCertsPath"; private static String MOUNT_PATH_FOR_SCENARIO = "mountPathForScenario"; + private static String MOCK_NOTIFICATION_CHANNEL = "mockNotificationChannel"; private static String SERVER_ERRORS_TO_MONITOR = "serverErrorsToMonitor"; @@ -207,6 +209,7 @@ public class ConfigManager { private static String esignetMockBaseURL; private static String sunBirdBaseURL; private static String esignetActuatorPropertySection; + private static String injiCertifyBaseURL; private static String dbPort; private static String dbDomain; @@ -247,6 +250,7 @@ public class ConfigManager { private static String mountPath; private static String authCertsPath; + private static String mockNotificationChannel; private static String mountPathForScenario; private static String packetUtilityBaseUrl; public static Properties propsKernel; @@ -361,6 +365,11 @@ public static void init() { mountPath = System.getenv(MOUNT_PATH) == null ? propsKernel.getProperty(MOUNT_PATH) : System.getenv(MOUNT_PATH); propsKernel.setProperty(MOUNT_PATH, mountPath); + mockNotificationChannel = System.getenv(MOCK_NOTIFICATION_CHANNEL) == null + ? propsKernel.getProperty(MOCK_NOTIFICATION_CHANNEL) + : System.getenv(MOCK_NOTIFICATION_CHANNEL); + propsKernel.setProperty(MOCK_NOTIFICATION_CHANNEL, mockNotificationChannel); + authCertsPath = System.getenv(AUTHCERTS_PATH) == null ? propsKernel.getProperty(AUTHCERTS_PATH) : System.getenv(AUTHCERTS_PATH); propsKernel.setProperty(AUTHCERTS_PATH, authCertsPath); @@ -420,6 +429,13 @@ public static void init() { eSignetbaseurl = System.getProperty("env.endpoint").replace("-internal", ""); } propsKernel.setProperty(ESIGNET_BASE_URL, eSignetbaseurl); + + if (System.getenv(INJI_CERTIFY_BASE_URL) != null) { + injiCertifyBaseURL = System.getenv(INJI_CERTIFY_BASE_URL); + } else { + injiCertifyBaseURL = System.getProperty("env.endpoint").replace("api-internal", "injicertify"); + } + propsKernel.setProperty(INJI_CERTIFY_BASE_URL, injiCertifyBaseURL); esignetMockBaseURL = System.getenv(ESIGNET_MOCK_BASE_URL) == null ? propsKernel.getProperty(ESIGNET_MOCK_BASE_URL) @@ -573,6 +589,11 @@ public static String getEsignetBaseUrl() { return eSignetbaseurl; } + + public static String getInjiCertifyBaseUrl() { + return injiCertifyBaseURL; + + } public static String getEsignetMockBaseURL() { return esignetMockBaseURL; @@ -615,6 +636,10 @@ public static String getmountPathForScenario() { public static String getpacketUtilityBaseUrl() { return packetUtilityBaseUrl; } + + public static String getMockNotificationChannel() { + return mockNotificationChannel; + } public static String getauthCertsPath() { return authCertsPath; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java index e8537588a6..7783523e2d 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java @@ -580,7 +580,8 @@ public static boolean doesResponseHasErrorCode(String responseString, String all } public static void reportServerIssues(String responseString, TestCaseDTO testCaseDTO) throws AdminTestException { - if (responseString.startsWith("")) + if (responseString.startsWith("") || responseString.startsWith(" Date: Tue, 27 Aug 2024 17:56:22 +0530 Subject: [PATCH 05/43] MOSIP-35082 - Fixed failure test cases Signed-off-by: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> --- .../io/mosip/testrig/apirig/utils/AdminTestUtil.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 718f2f0d7b..953b9c019c 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -4586,6 +4586,14 @@ private static ArrayList convertJson(String[] templateFields, String translatedValue = valueToConvert; isFilterRequired = true; } + } else if (jsonObject.has(GlobalConstants.KEYWORD_DATA)) { + String filterValueToConvert = jsonObject.getJSONArray(GlobalConstants.KEYWORD_DATA).get(0).toString(); + JSONObject filtervalue = new JSONObject(filterValueToConvert); + if (filtervalue.has(fieldToConvert)) { + valueToConvert = filtervalue.getString(fieldToConvert); + translatedValue = valueToConvert; + isFilterRequired = false; + } } if (!language.equalsIgnoreCase("eng") && valueToConvert != null) { @@ -7437,4 +7445,4 @@ public static boolean IsCertSyncd(String certSubjectSubString) { // } // return false; // } -} \ No newline at end of file +} From 26e9fbbb731a2ef1e7a31c968d38cb754a8af150 Mon Sep 17 00:00:00 2001 From: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:58:16 +0530 Subject: [PATCH 06/43] MOSIP-35082 - Fixed failure test cases Signed-off-by: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> --- .../java/io/mosip/testrig/apirig/utils/GlobalConstants.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java index 13241304d0..c26e9d0850 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java @@ -115,6 +115,7 @@ public class GlobalConstants { public static final String CERTIFICATEDATA = "certificateData"; public static final String PARTNERDOMAIN = "partnerDomain"; public static final String DATA = ".data"; + public static final String KEYWORD_DATA = "data"; public static final String AUTOMATION = "automation"; public static final String AUTOMATIONLABS = "@automationlabs.com"; public static final String REPSONSE = "Repsonse: %s %s%n"; @@ -227,4 +228,4 @@ public class GlobalConstants { public static final String SEND_OTP_ENDPOINT = "mimoto/req/"; public static final String CREATE_VID_ENDPOINT = "/idrepository/v1/vid"; public static final String MIMOTO_CREDENTIAL_STATUS = "Mimoto_CredentialsStatus_"; -} \ No newline at end of file +} From 345811d4e93ef31f7bcaaa4bc5ceb4489e1beb74 Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:52:09 +0530 Subject: [PATCH 07/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 64 +++++++++++++++++-- .../apirig/utils/KernelAuthentication.java | 2 +- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 727381f506..ffe020dff8 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -4971,6 +4971,8 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { requestJson.getJSONObject("request").put("registrationId", "{{registrationId}}"); JSONObject identityJson = new JSONObject(); identityJson.put("UIN", "{{UIN}}"); + JSONArray handleArray = new JSONArray(); + handleArray.put("handles"); List selectedHandles = new ArrayList<>(); //requiredPropsArray.put("functionalId"); @@ -4997,16 +4999,17 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { JSONObject eachValueJsonForHandles = new JSONObject(); if (eachRequiredProp.equals(emailResult)) { eachValueJsonForHandles.put("value", "$EMAILVALUE$"); - eachValueJsonForHandles.put("tags", ":[handle]"); + eachValueJsonForHandles.put("tags", handleArray); selectedHandles.add(emailResult); } else if (eachRequiredProp.equals(result)) { eachValueJsonForHandles.put("value", "$PHONENUMBERFORIDENTITY$"); - eachValueJsonForHandles.put("tags", ":[handle]"); + //"tags": ":["handle"] + eachValueJsonForHandles.put("tags", handleArray); selectedHandles.add(result); } else { - eachValueJsonForHandles.put("value", "1726266"); - eachValueJsonForHandles.put("tags", ":[handle]"); + eachValueJsonForHandles.put("value", RANDOM_ID); + eachValueJsonForHandles.put("tags", handleArray); selectedHandles.add(eachRequiredProp); } eachPropDataArrayForHandles.put(eachValueJsonForHandles); @@ -5076,7 +5079,10 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() } } } - identityJson.put("selectedHandles", selectedHandles); + if (selectedHandles != null) { + identityJson.put("selectedHandles", selectedHandles); + } + // Constructing and adding functionalIds JSONArray functionalIdsArray = new JSONArray(); @@ -7328,6 +7334,53 @@ public static boolean IsCertSyncd(String certSubjectSubString) { return false; } + public String replaceArrayHandleValues(String inputJson, String testCaseName) { + JSONObject jsonObj = new JSONObject(inputJson); + JSONObject request = jsonObj.getJSONObject("request"); + JSONObject identity = request.getJSONObject("identity"); + JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); + + for (int i = 0; i < selectedHandles.length(); i++) { + String handle = selectedHandles.getString(i); + if (identity.has(handle)) { + JSONArray handleArray = identity.getJSONArray(handle); + + if (testCaseName.endsWith("_onlywithtags")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("value"); + } + } else if (testCaseName.endsWith("_withoutvalues")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("tags"); + } + } + else if (testCaseName.endsWith("_withtagwithoutselectedhandles")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("tags"); + } + } + + else if (testCaseName.endsWith("_withoutselectedhandles")) { + identity.remove("selectedHandles"); + break; + } else { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.put("value", handleObj.getString("value") + "_modified"); + } + } + + identity.put(handle, handleArray); + } + } + + return jsonObj.toString(); + } + + // public static boolean checkIsCertTrusted(String certIssuer, JSONArray ArrayOfJsonObjects, int recursiveCount) { // for (int i = 0; i < ArrayOfJsonObjects.length(); i++) { // if (ArrayOfJsonObjects.getJSONObject(i).has("certSubject") @@ -7368,4 +7421,5 @@ public static boolean IsCertSyncd(String certSubjectSubString) { // } // return false; // } + } \ No newline at end of file diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java index 2537758499..f32f6b75a8 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/KernelAuthentication.java @@ -158,7 +158,7 @@ public String getTokenByRole(String role, String tokenType) { batchJobToken = kernelAuthLib.getPreRegToken(); return batchJobToken; - case "invalidBatch": + case "invalidbatch": if (!kernelCmnLib.isValidToken(invalidBatchJobToken)) invalidBatchJobToken = kernelAuthLib.getPreRegInvalidToken(); return invalidBatchJobToken; From 9f7f7baebe6a934248ddfe91505d56b0a3384d3f Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:40:56 +0530 Subject: [PATCH 08/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 153 +++++++++++++++++- .../testrig/apirig/utils/GlobalConstants.java | 1 + 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 737277d990..7c0032be20 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -141,6 +141,7 @@ public class AdminTestUtil extends BaseTestCase { public static String propsHealthCheckURL = MosipTestRunner.getGlobalResourcePath() + "/" + "config/healthCheckEndpoint.properties"; private static String serverComponentsCommitDetails; + private static boolean foundHandlesInIdSchema= false; protected static String token = null; String idToken = null; @@ -5139,6 +5140,7 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() } } if (selectedHandles != null) { + setfoundHandlesInIdSchema(true); identityJson.put("selectedHandles", selectedHandles); } @@ -6579,6 +6581,12 @@ public static String isTestCaseValidForExecution(TestCaseDTO testCaseDTO) { && (!isElementPresent(new JSONArray(schemaRequiredField), dob))) { throw new SkipException(GlobalConstants.FEATURE_NOT_SUPPORTED_MESSAGE); } + + if (testCaseName.startsWith("IdRepository_") && testCaseName.contains("_handle")) { + throw new SkipException(GlobalConstants.HANDLE_SCHEMA_NOT_DEPLOYED_MESSAGE); + } + + else if (testCaseName.startsWith("IdRepository_") && testCaseName.contains("Email") && (!isElementPresent(new JSONArray(schemaRequiredField), email))) { @@ -6636,6 +6644,8 @@ else if (testCaseName.startsWith("IdRepository_") && testCaseName.contains("Inva && BaseTestCase.currentModule.equalsIgnoreCase("resident") && testCaseName.contains("_SignJWT_")) { throw new SkipException("esignet module is not deployed"); } + + if ((ConfigManager.isInServiceNotDeployedList(GlobalConstants.ESIGNET)) && BaseTestCase.currentModule.equalsIgnoreCase("resident") @@ -7439,7 +7449,7 @@ public String replaceArrayHandleValues(String inputJson, String testCaseName) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("value"); } - } else if (testCaseName.endsWith("_withoutvalues")) { + } else if (testCaseName.endsWith("_withouttags")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("tags"); @@ -7448,14 +7458,143 @@ public String replaceArrayHandleValues(String inputJson, String testCaseName) { else if (testCaseName.endsWith("_withtagwithoutselectedhandles")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("selectedHandles"); + } + } + else if (testCaseName.endsWith("_withinvalidtag")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + JSONArray tags = handleObj.optJSONArray("tags"); + if (tags != null) { + for (int k = 0; k < tags.length(); k++) { + String tag = tags.getString(k); + tags.put(k, tag + "_invalid"+ RANDOM_ID); + } + handleObj.put("tags", tags); + } + } + } + + + else if (testCaseName.endsWith("_withmultiplevalues")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + JSONArray valuesArray = new JSONArray(); + valuesArray.put("mosip501724826584965_modified_1"); + valuesArray.put("mosip501724826584965_modified_2"); + valuesArray.put("mosip501724826584965_modified_3"); + handleObj.put("values", valuesArray); + + } + } + + else if (testCaseName.endsWith("_withmultiplevaluesandwithouttags")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + JSONArray valuesArray = new JSONArray(); + valuesArray.put("mosip501724826584965_modified_1"); + valuesArray.put("mosip501724826584965_modified_2"); + valuesArray.put("mosip501724826584965_modified_3"); + handleObj.put("values", valuesArray); handleObj.remove("tags"); } } + else if (testCaseName.endsWith("_withemptyselecthandles")) { + identity.put("selectedHandles", new JSONArray()); + } + + + else if (testCaseName.endsWith("_withoutselectedhandles")) { identity.remove("selectedHandles"); break; - } else { + } + + else if (testCaseName.endsWith("_withmultiplehandleswithoutvalue")) { + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + boolean containsPhone = false; + for (int j = 0; j < selectedHandles.length(); j++) { + if (result.equalsIgnoreCase(selectedHandles.getString(j))) { + containsPhone = true; + break; + } + } + if (!containsPhone) { + selectedHandles.put(result); + JSONObject phoneEntry = new JSONObject(); + phoneEntry.put("value", "$PHONENUMBERFORIDENTITY$"); + JSONArray phoneArray = new JSONArray(); + phoneArray.put(phoneEntry); + identity.put(result, phoneArray); + + } + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("value"); + } + } + + // Jayesh TCs + + else if (testCaseName.endsWith("_withfunctionalIds") && handle.equals("functionalId")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("tags"); + } + } else if (testCaseName.endsWith("_withfunctionalIdsUsedFirstTwoValue") && handle.equals("functionalId")) { + if (handleArray.length() < 3) { + JSONObject secondValue = new JSONObject(); + secondValue.put("value", "RANDOM_ID_2" + 12); + secondValue.put("tags", new JSONArray().put("handle")); + JSONObject thirdValue = new JSONObject(); + thirdValue.put("value", "RANDOM_ID_2" + 34); + handleArray.put(secondValue); + handleArray.put(thirdValue); + } + } else if (testCaseName.endsWith("_withfunctionalIdsandPhoneWithoutTags")) { + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + boolean containsPhone = false; + for (int j = 0; j < selectedHandles.length(); j++) { + if (result.equalsIgnoreCase(selectedHandles.getString(j))) { + containsPhone = true; + break; + } + } + if (!containsPhone) { + selectedHandles.put(result); + JSONObject phoneEntry = new JSONObject(); + phoneEntry.put("value", "$PHONENUMBERFORIDENTITY$"); + JSONArray phoneArray = new JSONArray(); + phoneArray.put(phoneEntry); + identity.put(result, phoneArray); + + } + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.remove("tags"); + } + } else if (testCaseName.endsWith("_withfunctionalIdsUsedFirstTwoValueOutOfFive")) { + String baseValue = ""; + if (handleArray.length() > 0) { + baseValue = handleArray.getJSONObject(0).getString("value"); + } + for (int j = 0; j < 4; j++) { + JSONObject handleObj = new JSONObject(); + if (j < 1) { + handleObj.put("value", baseValue + j); + handleObj.put("tags", new JSONArray().put("handle")); + } else { + handleObj.put("value", baseValue + j); + } + handleArray.put(handleObj); + } + } + + + else { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.put("value", handleObj.getString("value") + "_modified"); @@ -7468,6 +7607,16 @@ else if (testCaseName.endsWith("_withoutselectedhandles")) { return jsonObj.toString(); } + + public static void setfoundHandlesInIdSchema(boolean foundHandles) { + + foundHandlesInIdSchema=foundHandles; + } + + public static boolean isHandlesAvailableInIdSchema(boolean foundHandles) { + + return foundHandlesInIdSchema; + } // public static boolean checkIsCertTrusted(String certIssuer, JSONArray ArrayOfJsonObjects, int recursiveCount) { diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java index c26e9d0850..57b3bf2e48 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java @@ -212,6 +212,7 @@ public class GlobalConstants { public static final String VID_FEATURE_NOT_SUPPORTED = "VID feature not supported. Hence skipping the testcase"; public static final String UIN_FEATURE_NOT_SUPPORTED = "UIN feature not supported. Hence skipping the testcase"; public static final String FEATURE_NOT_SUPPORTED_MESSAGE = "feature not supported. Hence skipping the testcase"; + public static final String HANDLE_SCHEMA_NOT_DEPLOYED_MESSAGE = "ARRAY HANDLE Related Schema is not there Hence skipping the testcase"; public static final String VID_GENERATED_USING_RESIDENT_API_SO_FEATURE_NOT_SUPPORTED_OR_NEEDED_MESSAGE = "Generating VID using Resident API. So, this feature not supported/needed. Hence skipping the testcase"; public static final String SERVICE_NOT_DEPLOYED_MESSAGE = "Service not deployed. Hence skipping the testcase"; public static final String FEATURE_NOT_SUPPORTED = "feature not supported"; From 8c032546c4813c31f9cd8527084b33b0924c3a76 Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:34:39 +0530 Subject: [PATCH 09/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 370 +++++++++++++++++- 1 file changed, 351 insertions(+), 19 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 7c0032be20..94109c7a89 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -171,9 +171,9 @@ public class AdminTestUtil extends BaseTestCase { protected static String policygroupId = null; protected static String regDeviceResponse = null; protected static String generatedVID = null; - public static final String RANDOM_ID = "mosip" + generateRandomNumberString(2) + public String RANDOM_ID = "mosip" + generateRandomNumberString(2) + Calendar.getInstance().getTimeInMillis(); - public static final String RANDOM_ID_2 = "mosip" + generateRandomNumberString(2) + public final String RANDOM_ID_2 = "mosip" + generateRandomNumberString(2) + Calendar.getInstance().getTimeInMillis(); public static final String RANDOM_ID_V2 = "mosip" + generateRandomNumberString(2) + Calendar.getInstance().getTimeInMillis(); @@ -196,6 +196,7 @@ public class AdminTestUtil extends BaseTestCase { // public static EncryptionDecrptionUtil encryptDecryptUtil = null; protected static String idField = null; protected static String identityHbs = null; + protected static String updateIdentityHbs = null; protected static String draftHbs = null; protected static String preregHbsForCreate = null; protected static String preregHbsForUpdate = null; @@ -3008,6 +3009,9 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { jsonString = replaceKeywordWithValue(jsonString, "$POLICYNUMBERFORSUNBIRDRC$", properties.getProperty("policyNumberForSunBirdRC")); } + + + if (jsonString.contains("$FULLNAMEFORSUNBIRDRC$")) { jsonString = replaceKeywordWithValue(jsonString, "$FULLNAMEFORSUNBIRDRC$", fullNameForSunBirdRC); @@ -5068,7 +5072,7 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { eachValueJsonForHandles.put("tags", handleArray); selectedHandles.add(result); } else { - eachValueJsonForHandles.put("value", RANDOM_ID); + eachValueJsonForHandles.put("value", "$FUNCTIONALID$"); eachValueJsonForHandles.put("tags", handleArray); selectedHandles.add(eachRequiredProp); } @@ -5179,6 +5183,181 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() return identityHbs; } + + public static String updateIdentityHbs(boolean regenerateHbs) { + if (updateIdentityHbs != null && !regenerateHbs) { + + return updateIdentityHbs; + } + JSONObject requestJson = new JSONObject(); + kernelAuthLib = new KernelAuthentication(); + String token = kernelAuthLib.getTokenByRole(GlobalConstants.ADMIN); + String url = ApplnURI + properties.getProperty(GlobalConstants.MASTER_SCHEMA_URL); + + Response response = RestClient.getRequestWithCookie(url, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON, + GlobalConstants.AUTHORIZATION, token); + + org.json.JSONObject responseJson = new org.json.JSONObject(response.asString()); + org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); + + Double schemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + idSchemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + String schemaJsonData = schemaData.getString(GlobalConstants.SCHEMA_JSON); + + String schemaFile = schemaJsonData; + + boolean emailFieldAdditionallyAdded = false; + boolean phoneFieldAdditionallyAdded = false; + try { + JSONObject schemaFileJson = new JSONObject(schemaFile); + JSONObject schemaPropsJson = schemaFileJson.getJSONObject("properties"); + JSONObject schemaIdentityJson = schemaPropsJson.getJSONObject("identity"); + JSONObject identityPropsJson = schemaIdentityJson.getJSONObject("properties"); + JSONArray requiredPropsArray = schemaIdentityJson.getJSONArray("required"); + + schemaRequiredField = requiredPropsArray.toString(); + + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + + if (!isElementPresent(requiredPropsArray, result)) { + requiredPropsArray.put(result); + phoneFieldAdditionallyAdded = true; + } + if (identityPropsJson.has(result)) { + phoneSchemaRegex = identityPropsJson.getJSONObject(result).getJSONArray("validators") + .getJSONObject(0).getString("validator"); + } + + String email = getValueFromAuthActuator("json-property", "emailId"); + String emailResult = email.replaceAll("\\[\"|\"\\]", ""); + + if (!isElementPresent(requiredPropsArray, emailResult)) { + requiredPropsArray.put(emailResult); + emailFieldAdditionallyAdded = true; + } + + requestJson.put("id", "{{id}}"); + requestJson.put("status", "ACTIVATED"); + requestJson.put("request", new HashMap<>()); + requestJson.getJSONObject("request").put("registrationId", "{{registrationId}}"); + JSONObject identityJson = new JSONObject(); + identityJson.put("UIN", "{{UIN}}"); + JSONArray handleArray = new JSONArray(); + handleArray.put("handles"); + + List selectedHandles = new ArrayList<>(); + //requiredPropsArray.put("functionalId"); + for (int i = 0, size = requiredPropsArray.length(); i < size; i++) { + String eachRequiredProp = requiredPropsArray.getString(i); + + if (!identityPropsJson.has(eachRequiredProp)) { + continue; + } + + JSONObject eachPropDataJson = (JSONObject) identityPropsJson.get(eachRequiredProp); + String randomValue = ""; + if(eachRequiredProp == emailResult) { + randomValue ="shshssh"; + } + if(eachRequiredProp == result) { + randomValue =phoneSchemaRegex ; + } + + + // Processing for TaggedListType + if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString().contains("TaggedListType")) { + JSONArray eachPropDataArrayForHandles = new JSONArray(); + JSONObject eachValueJsonForHandles = new JSONObject(); + if (eachRequiredProp.equals(emailResult)) { + eachValueJsonForHandles.put("value", "$EMAILVALUE$"); + eachValueJsonForHandles.put("tags", handleArray); + selectedHandles.add(emailResult); + + } else if (eachRequiredProp.equals(result)) { + eachValueJsonForHandles.put("value", "$PHONENUMBERFORIDENTITY$"); + //"tags": ":["handle"] + eachValueJsonForHandles.put("tags", handleArray); + selectedHandles.add(result); + } else { + eachValueJsonForHandles.put("value", "$FUNCTIONALID$"); + eachValueJsonForHandles.put("tags", handleArray); + selectedHandles.add(eachRequiredProp); + } + eachPropDataArrayForHandles.put(eachValueJsonForHandles); + identityJson.put(eachRequiredProp, eachPropDataArrayForHandles); + + } + + + + else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString().contains("simpleType")) { + JSONArray eachPropDataArray = new JSONArray(); + + for (int j = 0; j < BaseTestCase.getLanguageList().size(); j++) { + if (BaseTestCase.getLanguageList().get(j) != null + && !BaseTestCase.getLanguageList().get(j).isEmpty()) { + JSONObject eachValueJson = new JSONObject(); + eachValueJson.put("language", BaseTestCase.getLanguageList().get(j)); + if (eachRequiredProp.contains(GlobalConstants.FULLNAME) && regenerateHbs == true) { + eachValueJson.put(GlobalConstants.VALUE, propsMap.getProperty(eachRequiredProp + "1")); + } else if (eachRequiredProp.contains(GlobalConstants.FIRST_NAME) && regenerateHbs == true) { + eachValueJson.put(GlobalConstants.VALUE, propsMap.getProperty(eachRequiredProp + 1)); + } else if (eachRequiredProp.contains(GlobalConstants.GENDER)) { + eachValueJson.put(GlobalConstants.VALUE, propsMap.getProperty(eachRequiredProp)); + } else { + eachValueJson.put(GlobalConstants.VALUE, + (propsMap.getProperty(eachRequiredProp) == null) ? "TEST_" + eachRequiredProp + : propsMap.getProperty(eachRequiredProp) + BaseTestCase.getLanguageList().get(j)); + } + eachPropDataArray.put(eachValueJson); + } + } + identityJson.put(eachRequiredProp, eachPropDataArray); + + } else { + if (eachRequiredProp.equals("IDSchemaVersion")) { + identityJson.put(eachRequiredProp, schemaVersion); + } + else if (eachRequiredProp.equals("individualBiometrics")) { + identityJson.remove("individualBiometrics"); + } + else if (eachRequiredProp.equals("proofOfIdentity")) { + identityJson.remove("proofOfIdentity"); + } + else { + identityJson.put(eachRequiredProp, "{{" + eachRequiredProp + "}}"); + } + } + } + if (selectedHandles != null) { + setfoundHandlesInIdSchema(true); + identityJson.put("selectedHandles", selectedHandles); + } + + + // Constructing and adding functionalIds + JSONArray functionalIdsArray = new JSONArray(); + for (String language : BaseTestCase.getLanguageList()) { + if (language != null && !language.isEmpty()) { + JSONObject functionalId = new JSONObject(); + functionalId.put("value", "TEST_CITY" + language); + functionalIdsArray.put(functionalId); + } + } + requestJson.getJSONObject("request").put("identity", identityJson); + requestJson.put("requesttime", "{{requesttime}}"); + requestJson.put("version", "{{version}}"); + + System.out.println(requestJson); + + } catch (Exception e) { + logger.error(e.getMessage()); + } + + updateIdentityHbs = requestJson.toString(); + return updateIdentityHbs; + } public static int generateLatestSchemaVersion() { @@ -6582,7 +6761,7 @@ public static String isTestCaseValidForExecution(TestCaseDTO testCaseDTO) { throw new SkipException(GlobalConstants.FEATURE_NOT_SUPPORTED_MESSAGE); } - if (testCaseName.startsWith("IdRepository_") && testCaseName.contains("_handle")) { + if (testCaseName.startsWith("IdRepository_") && testCaseName.contains("_handle") && foundHandlesInIdSchema == false) { throw new SkipException(GlobalConstants.HANDLE_SCHEMA_NOT_DEPLOYED_MESSAGE); } @@ -7438,30 +7617,52 @@ public String replaceArrayHandleValues(String inputJson, String testCaseName) { JSONObject request = jsonObj.getJSONObject("request"); JSONObject identity = request.getJSONObject("identity"); JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); + /* + * String handleValue = null; if (selectedHandles.length() == 0) { return null; + * } String firstHandleKey = selectedHandles.getString(0); + * + * for (int i = 0; i < selectedHandles.length(); i++) { String handle = + * selectedHandles.getString(i); + * + * if (handle.equals(firstHandleKey) && identity.has(firstHandleKey)) { + * JSONArray handleArray = identity.getJSONArray(firstHandleKey); + * + * if (handleArray.length() > 0) { JSONObject handleObj = + * handleArray.getJSONObject(0); handleValue = handleObj.getString("value"); } } + * } + */ for (int i = 0; i < selectedHandles.length(); i++) { String handle = selectedHandles.getString(i); if (identity.has(handle)) { JSONArray handleArray = identity.getJSONArray(handle); - if (testCaseName.endsWith("_onlywithtags")) { + if (testCaseName.contains("_onlywithtags")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("value"); } - } else if (testCaseName.endsWith("_withouttags")) { + } else if (testCaseName.contains("_withouttags")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("tags"); } - } - else if (testCaseName.endsWith("_withtagwithoutselectedhandles")) { + } + /* + * else if (testCaseName.endsWith("_withalreadyusedhandlevalue")) { for (int j = + * 0; j < handleArray.length(); j++) { JSONObject handleObj = + * handleArray.getJSONObject(j); JSONArray values = + * handleObj.optJSONArray("values"); if (values != null) { for (int k = 0; k < + * values.length(); k++) { String value1 = values.getString(k); values.put(k, + * value1 + handleValue); } handleObj.put("values", values); } } } + */ + else if (testCaseName.contains("_withtagwithoutselectedhandles")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("selectedHandles"); } } - else if (testCaseName.endsWith("_withinvalidtag")) { + else if (testCaseName.contains("_withinvalidtag")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); JSONArray tags = handleObj.optJSONArray("tags"); @@ -7476,7 +7677,7 @@ else if (testCaseName.endsWith("_withinvalidtag")) { } - else if (testCaseName.endsWith("_withmultiplevalues")) { + else if (testCaseName.contains("_withmultiplevalues")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); JSONArray valuesArray = new JSONArray(); @@ -7488,7 +7689,7 @@ else if (testCaseName.endsWith("_withmultiplevalues")) { } } - else if (testCaseName.endsWith("_withmultiplevaluesandwithouttags")) { + else if (testCaseName.contains("_withmultiplevaluesandwithouttags")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); JSONArray valuesArray = new JSONArray(); @@ -7500,18 +7701,18 @@ else if (testCaseName.endsWith("_withmultiplevaluesandwithouttags")) { } } - else if (testCaseName.endsWith("_withemptyselecthandles")) { + else if (testCaseName.contains("_withemptyselecthandles")) { identity.put("selectedHandles", new JSONArray()); } - else if (testCaseName.endsWith("_withoutselectedhandles")) { + else if (testCaseName.contains("_withoutselectedhandles")) { identity.remove("selectedHandles"); break; } - else if (testCaseName.endsWith("_withmultiplehandleswithoutvalue")) { + else if (testCaseName.contains("_withmultiplehandleswithoutvalue")) { String phone = getValueFromAuthActuator("json-property", "phone_number"); String result = phone.replaceAll("\\[\"|\"\\]", ""); boolean containsPhone = false; @@ -7538,12 +7739,12 @@ else if (testCaseName.endsWith("_withmultiplehandleswithoutvalue")) { // Jayesh TCs - else if (testCaseName.endsWith("_withfunctionalIds") && handle.equals("functionalId")) { + else if (testCaseName.contains("_withfunctionalIds") && handle.equals("functionalId")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("tags"); } - } else if (testCaseName.endsWith("_withfunctionalIdsUsedFirstTwoValue") && handle.equals("functionalId")) { + } else if (testCaseName.contains("_withfunctionalIdsUsedFirstTwoValue") && handle.equals("functionalId")) { if (handleArray.length() < 3) { JSONObject secondValue = new JSONObject(); secondValue.put("value", "RANDOM_ID_2" + 12); @@ -7553,7 +7754,7 @@ else if (testCaseName.endsWith("_withfunctionalIds") && handle.equals("functiona handleArray.put(secondValue); handleArray.put(thirdValue); } - } else if (testCaseName.endsWith("_withfunctionalIdsandPhoneWithoutTags")) { + } else if (testCaseName.contains("_withfunctionalIdsandPhoneWithoutTags")) { String phone = getValueFromAuthActuator("json-property", "phone_number"); String result = phone.replaceAll("\\[\"|\"\\]", ""); boolean containsPhone = false; @@ -7576,7 +7777,7 @@ else if (testCaseName.endsWith("_withfunctionalIds") && handle.equals("functiona JSONObject handleObj = handleArray.getJSONObject(j); handleObj.remove("tags"); } - } else if (testCaseName.endsWith("_withfunctionalIdsUsedFirstTwoValueOutOfFive")) { + } else if (testCaseName.contains("_withfunctionalIdsUsedFirstTwoValueOutOfFive")) { String baseValue = ""; if (handleArray.length() > 0) { baseValue = handleArray.getJSONObject(0).getString("value"); @@ -7597,7 +7798,7 @@ else if (testCaseName.endsWith("_withfunctionalIds") && handle.equals("functiona else { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.put("value", handleObj.getString("value") + "_modified"); + handleObj.put("value", handleObj.getString("value")); } } @@ -7608,6 +7809,137 @@ else if (testCaseName.endsWith("_withfunctionalIds") && handle.equals("functiona return jsonObj.toString(); } + public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String testCaseName) { + JSONObject jsonObj = new JSONObject(inputJson); + JSONObject request = jsonObj.getJSONObject("request"); + JSONObject identity = request.getJSONObject("identity"); + JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); + + for (int i = 0; i < selectedHandles.length(); i++) { + String handle = selectedHandles.getString(i); + if (identity.has(handle)) { + JSONArray handleArray = identity.getJSONArray(handle); + + // Check and update based on the testCaseName + if (testCaseName.contains("_withupdatevalues")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONArray handleDetailsArray = identity.getJSONArray(handle); + for (int k = 0; k < handleDetailsArray.length(); k++) { + JSONObject handleDetails = handleDetailsArray.getJSONObject(k); + handleDetails.put("value", "mosip" + RANDOM_ID + "_" + j + "_" + k); + } + } + } else if (testCaseName.contains("_withmultiplevalues")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + JSONArray valuesArray = new JSONArray(); + valuesArray.put("mosip501724826584965_modified_1"); + valuesArray.put("mosip501724826584965_modified_2"); + valuesArray.put("mosip501724826584965_modified_3"); + handleObj.put("values", valuesArray); + } + } else if (testCaseName.contains("_withupdatetags")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + JSONArray tags = handleObj.optJSONArray("tags"); + if (tags != null) { + for (int k = 0; k < tags.length(); k++) { + String tag = tags.getString(k); + tags.put(k, tag + "_invalid" + RANDOM_ID); + } + handleObj.put("tags", tags); + } + } + } else if (testCaseName.contains("_withupdatetagsandhandles")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + JSONArray tags = handleObj.optJSONArray("tags"); + JSONArray values = handleObj.optJSONArray("value"); + if (tags != null) { + for (int k = 0; k < tags.length(); k++) { + String tag = tags.getString(k); + tags.put(k, tag + "_invalid" + RANDOM_ID); + } + handleObj.put("tags", tags); + } + if (values != null) { + for (int k = 0; k < values.length(); k++) { + String value = values.getString(k); + values.put(k, value + "_invalid" + RANDOM_ID); + } + handleObj.put("value", values); + } + } + } else if (testCaseName.contains("_withmultipledemohandles")) { + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + boolean containsPhone = false; + for (int j = 0; j < selectedHandles.length(); j++) { + if (result.equalsIgnoreCase(selectedHandles.getString(j))) { + containsPhone = true; + break; + } + } + if (!containsPhone) { + selectedHandles.put(result); + JSONObject phoneEntry = new JSONObject(); + phoneEntry.put("value", "$PHONENUMBERFORIDENTITY$"); + JSONArray phoneArray = new JSONArray(); + phoneArray.put(phoneEntry); + identity.put(result, phoneArray); + } + for (int j = 0; j < handleArray.length(); j++) { + JSONObject handleObj = handleArray.getJSONObject(j); + } + } + + else if (testCaseName.contains("_withdeletehandlefromrecord")) { + for (int j = 0; j < selectedHandles.length(); j++) { + String handleToDelete = selectedHandles.getString(j); + + if (identity.has(handleToDelete)) { + identity.remove(handleToDelete); + } + } + identity.remove("selectedHandles"); + } + else if (testCaseName.contains("_withupdatedselectedhandle")) { + String firstHandle = selectedHandles.getString(0); + String updatedHandle = firstHandle + RANDOM_ID; + selectedHandles.put(0, updatedHandle); + } + + else if (testCaseName.contains("_withupdatedselectedhandleanddemo")) { + if (selectedHandles.length() > 0) { + String originalHandle = selectedHandles.getString(0); + String updatedHandle = originalHandle + RANDOM_ID; + selectedHandles.put(0, updatedHandle); + if (identity.has(originalHandle)) { + JSONArray originalHandleArray = identity.getJSONArray(originalHandle); + for (int J = 0; J < originalHandleArray.length(); J++) { + JSONObject handleObject = originalHandleArray.getJSONObject(i); + String originalValue = handleObject.optString("value", ""); + handleObject.put("value", originalValue + RANDOM_ID); + originalHandleArray.put(J, handleObject); + } + identity.remove(originalHandle); + identity.put(updatedHandle, originalHandleArray); + } + } + } + + + + identity.put(handle, handleArray); + } + } + + // Return the modified JSON as a string + return jsonObj.toString(); + } + + + public static void setfoundHandlesInIdSchema(boolean foundHandles) { foundHandlesInIdSchema=foundHandles; From bd546647a1ce24028365f10568de0729823d1cc2 Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:52:45 +0530 Subject: [PATCH 10/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 451 ++++++++++-------- 1 file changed, 254 insertions(+), 197 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 94109c7a89..340c9d166d 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -5084,6 +5084,9 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString().contains("simpleType")) { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } JSONArray eachPropDataArray = new JSONArray(); for (int j = 0; j < BaseTestCase.getLanguageList().size(); j++) { @@ -5109,6 +5112,7 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() } else { if (eachRequiredProp.equals("proofOfIdentity")) { + identityJson.put(eachRequiredProp, new HashMap<>()); identityJson.getJSONObject(eachRequiredProp).put("format", "txt"); identityJson.getJSONObject(eachRequiredProp).put("type", "DOC001"); @@ -5125,7 +5129,11 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() eachPropDataJson.getJSONArray("validators").getJSONObject(0).getString("validator"))); } else if (eachRequiredProp.equals(result)) { identityJson.put(eachRequiredProp, "$PHONENUMBERFORIDENTITY$"); - } else if (eachRequiredProp.equals("password")) { + } else if (eachRequiredProp.equals(emailResult)) { + identityJson.put(eachRequiredProp, "$EMAILVALUE$"); + } + + else if (eachRequiredProp.equals("password")) { identityJson.put(eachRequiredProp, new HashMap<>()); if (addIdentityPassword.isBlank() && addIdentitySalt.isBlank()) getPasswordSaltFromKeyManager(PASSWORD_FOR_ADDIDENTITY_AND_REGISTRATION); @@ -5139,6 +5147,9 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() } else if (eachRequiredProp.equals("IDSchemaVersion")) { identityJson.put(eachRequiredProp, schemaVersion); } else { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } identityJson.put(eachRequiredProp, "{{" + eachRequiredProp + "}}"); } } @@ -5292,6 +5303,9 @@ public static String updateIdentityHbs(boolean regenerateHbs) { else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString().contains("simpleType")) { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } JSONArray eachPropDataArray = new JSONArray(); for (int j = 0; j < BaseTestCase.getLanguageList().size(); j++) { @@ -5322,10 +5336,19 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() else if (eachRequiredProp.equals("individualBiometrics")) { identityJson.remove("individualBiometrics"); } + else if (eachRequiredProp.equals(emailResult)) { + identityJson.put(eachRequiredProp, "$EMAILVALUE$"); + } + else if (eachRequiredProp.equals(result)) { + identityJson.put(eachRequiredProp, "$PHONENUMBERFORIDENTITY$"); + } else if (eachRequiredProp.equals("proofOfIdentity")) { identityJson.remove("proofOfIdentity"); } else { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } identityJson.put(eachRequiredProp, "{{" + eachRequiredProp + "}}"); } } @@ -7617,197 +7640,157 @@ public String replaceArrayHandleValues(String inputJson, String testCaseName) { JSONObject request = jsonObj.getJSONObject("request"); JSONObject identity = request.getJSONObject("identity"); JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); - /* - * String handleValue = null; if (selectedHandles.length() == 0) { return null; - * } String firstHandleKey = selectedHandles.getString(0); - * - * for (int i = 0; i < selectedHandles.length(); i++) { String handle = - * selectedHandles.getString(i); - * - * if (handle.equals(firstHandleKey) && identity.has(firstHandleKey)) { - * JSONArray handleArray = identity.getJSONArray(firstHandleKey); - * - * if (handleArray.length() > 0) { JSONObject handleObj = - * handleArray.getJSONObject(0); handleValue = handleObj.getString("value"); } } - * } - */ for (int i = 0; i < selectedHandles.length(); i++) { String handle = selectedHandles.getString(i); + if (identity.has(handle)) { - JSONArray handleArray = identity.getJSONArray(handle); + Object handleObj = identity.get(handle); // Dynamically get the handle object - if (testCaseName.contains("_onlywithtags")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.remove("value"); - } - } else if (testCaseName.contains("_withouttags")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.remove("tags"); - } - } - /* - * else if (testCaseName.endsWith("_withalreadyusedhandlevalue")) { for (int j = - * 0; j < handleArray.length(); j++) { JSONObject handleObj = - * handleArray.getJSONObject(j); JSONArray values = - * handleObj.optJSONArray("values"); if (values != null) { for (int k = 0; k < - * values.length(); k++) { String value1 = values.getString(k); values.put(k, - * value1 + handleValue); } handleObj.put("values", values); } } } - */ - else if (testCaseName.contains("_withtagwithoutselectedhandles")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.remove("selectedHandles"); - } - } - else if (testCaseName.contains("_withinvalidtag")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - JSONArray tags = handleObj.optJSONArray("tags"); - if (tags != null) { - for (int k = 0; k < tags.length(); k++) { - String tag = tags.getString(k); - tags.put(k, tag + "_invalid"+ RANDOM_ID); + // Check if the handle is an array + if (handleObj instanceof JSONArray) { + JSONArray handleArray = (JSONArray) handleObj; + + if (testCaseName.contains("_onlywithtags")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("value"); + } + } else if (testCaseName.contains("_withouttags")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("tags"); + } + } else if (testCaseName.contains("_withtagwithoutselectedhandles")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("selectedHandles"); + } + } else if (testCaseName.contains("_withinvalidtag")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + JSONArray tags = obj.optJSONArray("tags"); + if (tags != null) { + for (int k = 0; k < tags.length(); k++) { + String tag = tags.getString(k); + tags.put(k, tag + "_invalid" + "RANDOM_ID"); + } + obj.put("tags", tags); } - handleObj.put("tags", tags); } - } - } - - - else if (testCaseName.contains("_withmultiplevalues")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - JSONArray valuesArray = new JSONArray(); - valuesArray.put("mosip501724826584965_modified_1"); - valuesArray.put("mosip501724826584965_modified_2"); - valuesArray.put("mosip501724826584965_modified_3"); - handleObj.put("values", valuesArray); - - } - } - - else if (testCaseName.contains("_withmultiplevaluesandwithouttags")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - JSONArray valuesArray = new JSONArray(); - valuesArray.put("mosip501724826584965_modified_1"); - valuesArray.put("mosip501724826584965_modified_2"); - valuesArray.put("mosip501724826584965_modified_3"); - handleObj.put("values", valuesArray); - handleObj.remove("tags"); - } - } - - else if (testCaseName.contains("_withemptyselecthandles")) { - identity.put("selectedHandles", new JSONArray()); - } - - - - else if (testCaseName.contains("_withoutselectedhandles")) { - identity.remove("selectedHandles"); - break; - } - - else if (testCaseName.contains("_withmultiplehandleswithoutvalue")) { - String phone = getValueFromAuthActuator("json-property", "phone_number"); - String result = phone.replaceAll("\\[\"|\"\\]", ""); - boolean containsPhone = false; - for (int j = 0; j < selectedHandles.length(); j++) { - if (result.equalsIgnoreCase(selectedHandles.getString(j))) { - containsPhone = true; - break; + } else if (testCaseName.contains("_withmultiplevalues")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + JSONArray valuesArray = new JSONArray(); + valuesArray.put("mosip501724826584965_modified_1"); + valuesArray.put("mosip501724826584965_modified_2"); + valuesArray.put("mosip501724826584965_modified_3"); + obj.put("values", valuesArray); } - } - if (!containsPhone) { - selectedHandles.put(result); + } else if (testCaseName.contains("_withmultiplevaluesandwithouttags")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + JSONArray valuesArray = new JSONArray(); + valuesArray.put("mosip501724826584965_modified_1"); + valuesArray.put("mosip501724826584965_modified_2"); + valuesArray.put("mosip501724826584965_modified_3"); + obj.put("values", valuesArray); + obj.remove("tags"); + } + } else if (testCaseName.contains("_withemptyselecthandles")) { + identity.put("selectedHandles", new JSONArray()); + } else if (testCaseName.contains("_withoutselectedhandles")) { + identity.remove("selectedHandles"); + break; + } else if (testCaseName.contains("_withmultiplehandleswithoutvalue")) { + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + boolean containsPhone = false; + for (int j = 0; j < selectedHandles.length(); j++) { + if (result.equalsIgnoreCase(selectedHandles.getString(j))) { + containsPhone = true; + break; + } + } + if (!containsPhone) { + selectedHandles.put(result); JSONObject phoneEntry = new JSONObject(); phoneEntry.put("value", "$PHONENUMBERFORIDENTITY$"); JSONArray phoneArray = new JSONArray(); phoneArray.put(phoneEntry); identity.put(result, phoneArray); - - } - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.remove("value"); - } - } - - // Jayesh TCs - - else if (testCaseName.contains("_withfunctionalIds") && handle.equals("functionalId")) { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.remove("tags"); - } - } else if (testCaseName.contains("_withfunctionalIdsUsedFirstTwoValue") && handle.equals("functionalId")) { - if (handleArray.length() < 3) { - JSONObject secondValue = new JSONObject(); - secondValue.put("value", "RANDOM_ID_2" + 12); - secondValue.put("tags", new JSONArray().put("handle")); - JSONObject thirdValue = new JSONObject(); - thirdValue.put("value", "RANDOM_ID_2" + 34); - handleArray.put(secondValue); - handleArray.put(thirdValue); - } - } else if (testCaseName.contains("_withfunctionalIdsandPhoneWithoutTags")) { - String phone = getValueFromAuthActuator("json-property", "phone_number"); - String result = phone.replaceAll("\\[\"|\"\\]", ""); - boolean containsPhone = false; - for (int j = 0; j < selectedHandles.length(); j++) { - if (result.equalsIgnoreCase(selectedHandles.getString(j))) { - containsPhone = true; - break; } - } - if (!containsPhone) { - selectedHandles.put(result); + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("value"); + } + } else if (testCaseName.contains("_withfunctionalIds") && handle.equals("functionalId")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("tags"); + } + } else if (testCaseName.contains("_withfunctionalIdsUsedFirstTwoValue") && handle.equals("functionalId")) { + if (handleArray.length() < 3) { + JSONObject secondValue = new JSONObject(); + secondValue.put("value", "RANDOM_ID_2" + 12); + secondValue.put("tags", new JSONArray().put("handle")); + JSONObject thirdValue = new JSONObject(); + thirdValue.put("value", "RANDOM_ID_2" + 34); + handleArray.put(secondValue); + handleArray.put(thirdValue); + } + } else if (testCaseName.contains("_withfunctionalIdsandPhoneWithoutTags")) { + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + boolean containsPhone = false; + for (int j = 0; j < selectedHandles.length(); j++) { + if (result.equalsIgnoreCase(selectedHandles.getString(j))) { + containsPhone = true; + break; + } + } + if (!containsPhone) { + selectedHandles.put(result); JSONObject phoneEntry = new JSONObject(); phoneEntry.put("value", "$PHONENUMBERFORIDENTITY$"); JSONArray phoneArray = new JSONArray(); phoneArray.put(phoneEntry); identity.put(result, phoneArray); - - } - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.remove("tags"); - } - } else if (testCaseName.contains("_withfunctionalIdsUsedFirstTwoValueOutOfFive")) { - String baseValue = ""; - if (handleArray.length() > 0) { - baseValue = handleArray.getJSONObject(0).getString("value"); - } - for (int j = 0; j < 4; j++) { - JSONObject handleObj = new JSONObject(); - if (j < 1) { - handleObj.put("value", baseValue + j); - handleObj.put("tags", new JSONArray().put("handle")); - } else { - handleObj.put("value", baseValue + j); } - handleArray.put(handleObj); + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("tags"); + } + } else if (testCaseName.contains("_withfunctionalIdsUsedFirstTwoValueOutOfFive")) { + String baseValue = ""; + if (handleArray.length() > 0) { + baseValue = handleArray.getJSONObject(0).getString("value"); + } + for (int j = 0; j < 4; j++) { + JSONObject obj = new JSONObject(); + if (j < 1) { + obj.put("value", baseValue + j); + obj.put("tags", new JSONArray().put("handle")); + } else { + obj.put("value", baseValue + j); + } + handleArray.put(obj); + } + } else { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.put("value", obj.getString("value")); + } } - } - - else { - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - handleObj.put("value", handleObj.getString("value")); - } + identity.put(handle, handleArray); } - - identity.put(handle, handleArray); } } return jsonObj.toString(); } + public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String testCaseName) { JSONObject jsonObj = new JSONObject(inputJson); @@ -7815,19 +7798,19 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String JSONObject identity = request.getJSONObject("identity"); JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); + // Iterate over each handle in the selectedHandles array for (int i = 0; i < selectedHandles.length(); i++) { String handle = selectedHandles.getString(i); - if (identity.has(handle)) { + + // Check if the handle exists in identity and if its value is a JSONArray + if (identity.has(handle) && identity.get(handle) instanceof JSONArray) { JSONArray handleArray = identity.getJSONArray(handle); - // Check and update based on the testCaseName + // Process based on testCaseName if (testCaseName.contains("_withupdatevalues")) { for (int j = 0; j < handleArray.length(); j++) { - JSONArray handleDetailsArray = identity.getJSONArray(handle); - for (int k = 0; k < handleDetailsArray.length(); k++) { - JSONObject handleDetails = handleDetailsArray.getJSONObject(k); - handleDetails.put("value", "mosip" + RANDOM_ID + "_" + j + "_" + k); - } + JSONObject handleObj = handleArray.getJSONObject(j); + handleObj.put("value", "mosip" + RANDOM_ID + "_" + j); } } else if (testCaseName.contains("_withmultiplevalues")) { for (int j = 0; j < handleArray.length(); j++) { @@ -7844,33 +7827,28 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String JSONArray tags = handleObj.optJSONArray("tags"); if (tags != null) { for (int k = 0; k < tags.length(); k++) { - String tag = tags.getString(k); - tags.put(k, tag + "_invalid" + RANDOM_ID); + tags.put(k, tags.getString(k) + "_invalid" + RANDOM_ID); } - handleObj.put("tags", tags); } } } else if (testCaseName.contains("_withupdatetagsandhandles")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); JSONArray tags = handleObj.optJSONArray("tags"); - JSONArray values = handleObj.optJSONArray("value"); if (tags != null) { for (int k = 0; k < tags.length(); k++) { - String tag = tags.getString(k); - tags.put(k, tag + "_invalid" + RANDOM_ID); + tags.put(k, tags.getString(k) + "_invalid" + RANDOM_ID); } - handleObj.put("tags", tags); } + JSONArray values = handleObj.optJSONArray("value"); if (values != null) { for (int k = 0; k < values.length(); k++) { - String value = values.getString(k); - values.put(k, value + "_invalid" + RANDOM_ID); + values.put(k, values.getString(k) + "_invalid" + RANDOM_ID); } - handleObj.put("value", values); } } } else if (testCaseName.contains("_withmultipledemohandles")) { + // Handle specific demo handles by checking and adding them to the selectedHandles String phone = getValueFromAuthActuator("json-property", "phone_number"); String result = phone.replaceAll("\\[\"|\"\\]", ""); boolean containsPhone = false; @@ -7888,30 +7866,21 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String phoneArray.put(phoneEntry); identity.put(result, phoneArray); } - for (int j = 0; j < handleArray.length(); j++) { - JSONObject handleObj = handleArray.getJSONObject(j); - } - } - - else if (testCaseName.contains("_withdeletehandlefromrecord")) { + } else if (testCaseName.contains("_withdeletehandlefromrecord")) { for (int j = 0; j < selectedHandles.length(); j++) { String handleToDelete = selectedHandles.getString(j); - if (identity.has(handleToDelete)) { identity.remove(handleToDelete); } } identity.remove("selectedHandles"); - } - else if (testCaseName.contains("_withupdatedselectedhandle")) { - String firstHandle = selectedHandles.getString(0); - String updatedHandle = firstHandle + RANDOM_ID; - selectedHandles.put(0, updatedHandle); - } - - else if (testCaseName.contains("_withupdatedselectedhandleanddemo")) { + } else if (testCaseName.contains("_withupdatedselectedhandle")) { + String firstHandle = selectedHandles.getString(0); + String updatedHandle = firstHandle + RANDOM_ID; + selectedHandles.put(0, updatedHandle); + } else if (testCaseName.contains("_withupdatedselectedhandleanddemo")) { if (selectedHandles.length() > 0) { - String originalHandle = selectedHandles.getString(0); + String originalHandle = selectedHandles.getString(0); String updatedHandle = originalHandle + RANDOM_ID; selectedHandles.put(0, updatedHandle); if (identity.has(originalHandle)) { @@ -7926,9 +7895,95 @@ else if (testCaseName.contains("_withupdatedselectedhandleanddemo")) { identity.put(updatedHandle, originalHandleArray); } } + } else if (testCaseName.contains("_withupdatedselectedhandleandfirstattribute")) { + Iterator keys = identity.keys(); + if (keys.hasNext()) { + String firstKey = keys.next(); + if (!firstKey.equals("selectedHandles")) { + selectedHandles.put(0, firstKey); + if (identity.has(firstKey)) { + JSONArray originalArray = identity.getJSONArray(firstKey); + for (int j = 0; j < originalArray.length(); j++) { + JSONObject handleObject = originalArray.getJSONObject(j); + if (handleObject.has("value")) { + String originalValue = handleObject.getString("value"); + handleObject.put("value", originalValue + "123"); + } + if (handleObject.has("tags")) { + JSONArray tagsArray = handleObject.getJSONArray("tags"); + for (int k = 0; k < tagsArray.length(); k++) { + String tag = tagsArray.getString(k); + tagsArray.put(k, tag + "123"); + } + handleObject.put("tags", tagsArray); + } + originalArray.put(j, handleObject); + } + identity.remove(firstKey); + identity.put(firstKey, originalArray); + } + } + } } + else if (testCaseName.contains("_withremovedtaggedattribute")) { + for (int j = 0; j < selectedHandles.length(); j++) { + String handle1 = selectedHandles.getString(j); + + if (identity.has(handle1) && identity.get(handle1) instanceof JSONArray) { + JSONArray handleArray1 = identity.getJSONArray(handle1); + for (int k = 0; k < handleArray1.length(); k++) { + JSONObject handleObject = handleArray1.getJSONObject(k); + if (handleObject.has("tags")) { + handleObject.remove("tags"); + } + } + identity.put(handle, handleArray); + } + } + } + else if (testCaseName.contains("_withemptyhandles")) { + identity.remove("selectedHandles"); + + } + + else if (testCaseName.contains("_withouthandlesattr")) { + if (identity.has("selectedHandles")) { + selectedHandles = identity.getJSONArray("selectedHandles"); + for (int j = 0; j < selectedHandles.length(); j++) { + handle = selectedHandles.getString(j); + if (identity.has(handle)) { + identity.remove(handle); + } + } + identity.remove("selectedHandles"); + } + } + else if (testCaseName.contains("_witharandomnonhandleattr")) { + if (identity.has("selectedHandles")) { + List existingHandles = new ArrayList<>(); + for (int j = 0; i < selectedHandles.length(); i++) { + existingHandles.add(selectedHandles.getString(i)); + } + Iterator keys = identity.keys(); + while (keys.hasNext()) { + String key = keys.next(); + if (key.equals("selectedHandles")) { + continue; + } + if (!existingHandles.contains(key)) { + selectedHandles.put(key); + break; + } + } + } + } + + + + + identity.put(handle, handleArray); } @@ -7938,6 +7993,8 @@ else if (testCaseName.contains("_withupdatedselectedhandleanddemo")) { return jsonObj.toString(); } + + public static void setfoundHandlesInIdSchema(boolean foundHandles) { From dfc46645e32d74ab7283aa6706acb9da6b1df09c Mon Sep 17 00:00:00 2001 From: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:51:13 +0530 Subject: [PATCH 11/43] MOSIP-35592 - Updated the version in POM Signed-off-by: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> --- apitest-commons/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index b02e1cb180..de8143482a 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -8,7 +8,7 @@ apitest-commons Parent project of MOSIP functional tests https://github.com/mosip/mosip-functional-tests - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT @@ -39,7 +39,7 @@ UTF-8 - 1.2.0.1-SNAPSHOT + 1.2.0.1 11 @@ -191,7 +191,7 @@ 3.0.7 6.11 1.13 - apitest-commons-1.2.1-SNAPSHOT-jar-with-dependencies + apitest-commons-1.2.2-SNAPSHOT-jar-with-dependencies @@ -579,7 +579,7 @@ io.mosip.kernel kernel-core - 1.2.0.1-SNAPSHOT + 1.2.0.1 org.springframework.boot @@ -602,13 +602,13 @@ io.mosip.kernel kernel-templatemanager-velocity - 1.2.0.1-SNAPSHOT + 1.2.0.1 io.mosip.kernel kernel-keymanager-service - 1.2.0.1-SNAPSHOT + 1.2.0.1 lib From 362f89f95f7586e084695e4b7e9ce9062d8916d0 Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:10:56 +0530 Subject: [PATCH 12/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 340c9d166d..147432d08b 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -5128,8 +5128,14 @@ else if (eachPropDataJson.has("$ref") && eachPropDataJson.get("$ref").toString() identityJson.put(eachRequiredProp, genStringAsperRegex( eachPropDataJson.getJSONArray("validators").getJSONObject(0).getString("validator"))); } else if (eachRequiredProp.equals(result)) { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } identityJson.put(eachRequiredProp, "$PHONENUMBERFORIDENTITY$"); } else if (eachRequiredProp.equals(emailResult)) { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } identityJson.put(eachRequiredProp, "$EMAILVALUE$"); } @@ -5337,9 +5343,15 @@ else if (eachRequiredProp.equals("individualBiometrics")) { identityJson.remove("individualBiometrics"); } else if (eachRequiredProp.equals(emailResult)) { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } identityJson.put(eachRequiredProp, "$EMAILVALUE$"); } else if (eachRequiredProp.equals(result)) { + if(eachPropDataJson.has("handle")){ + selectedHandles.add(eachRequiredProp); + } identityJson.put(eachRequiredProp, "$PHONENUMBERFORIDENTITY$"); } else if (eachRequiredProp.equals("proofOfIdentity")) { @@ -7776,7 +7788,48 @@ public String replaceArrayHandleValues(String inputJson, String testCaseName) { } handleArray.put(obj); } - } else { + } + //43 in update identity + else if (testCaseName.contains("_removeexceptfirsthandle")) { + if (identity.has("selectedHandles")) { + + if (selectedHandles.length() > 0) { + String firstHandleToKeep = selectedHandles.getString(0); + + for (int j = 1; j < selectedHandles.length(); j++) { + if (identity.has(handle)) { + identity.remove(handle); + } + } + while (selectedHandles.length() > 1) { + selectedHandles.remove(1); + } + } + } + } + //44 in update identity + else if (testCaseName.contains("_withinvaliddemofield_inupdate")) { + if (identity.has("selectedHandles")) { + + if (selectedHandles.length() > 0) { + String firstHandleToKeep = selectedHandles.getString(0); + + for (int j = 1; j < selectedHandles.length(); j++) { + if (identity.has(handle)) { + identity.remove(handle); + } + } + while (selectedHandles.length() > 1) { + selectedHandles.remove(1); + } + } + } + } + + + + + else { for (int j = 0; j < handleArray.length(); j++) { JSONObject obj = handleArray.getJSONObject(j); obj.put("value", obj.getString("value")); @@ -7806,7 +7859,6 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String if (identity.has(handle) && identity.get(handle) instanceof JSONArray) { JSONArray handleArray = identity.getJSONArray(handle); - // Process based on testCaseName if (testCaseName.contains("_withupdatevalues")) { for (int j = 0; j < handleArray.length(); j++) { JSONObject handleObj = handleArray.getJSONObject(j); @@ -7960,6 +8012,45 @@ else if (testCaseName.contains("_withouthandlesattr")) { } } + //44 + else if (testCaseName.contains("_withinvaliddemofield")) { + if (identity.has("selectedHandles")) { + for (int j = 0; j < selectedHandles.length(); j++) { + if (identity.has(handle)) { + Object currentValue = identity.get(handle); + if (currentValue instanceof String) { + identity.put(handle, "invalid_" + currentValue); + } else if (currentValue instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) currentValue; + for (int k = 0; k < jsonArray.length(); k++) { + JSONObject obj = jsonArray.getJSONObject(k); + if (obj.has("value")) { + obj.put("value", "invalid_" + obj.getString("value")); + } + } + identity.put(handle, jsonArray); + } + } + selectedHandles.put(i, "invalid_" + handle); + } + identity.put("selectedHandles", selectedHandles); + } + } + //49 + else if (testCaseName.contains("_withoutselectedhandlesandattri")) { + + for (int j = 0; j < selectedHandles.length(); j++) { + + if (identity.has(handle)) { + identity.remove(handle); + } + } + + identity.remove("selectedHandles"); + } + + + else if (testCaseName.contains("_witharandomnonhandleattr")) { if (identity.has("selectedHandles")) { List existingHandles = new ArrayList<>(); From 3c56ad17875acc42625a2ef53eab3020fe7d70a2 Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Wed, 4 Sep 2024 13:03:21 +0530 Subject: [PATCH 13/43] MOSIP-33742 Signed-off-by: Nandhukumar --- .../testrig/apirig/utils/AdminTestUtil.java | 25 +++++++++++-------- .../testrig/apirig/utils/ConfigManager.java | 2 +- .../apirig/utils/OutputValidationUtil.java | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 718f2f0d7b..a77e873def 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -3574,8 +3574,11 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { if (request.has("client_id")) { clientId = request.get("client_id").toString(); } + + String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint", ConfigManager.getEsignetBaseUrl()); + jsonString = replaceKeywordWithValue(jsonString, "$CLIENT_ASSERTION_JWK$", - signJWKKey(clientId, oidcJWKKey1)); + signJWKKey(clientId, oidcJWKKey1, tempUrl)); } if (jsonString.contains("$CLIENT_ASSERTION_USER3_JWK$")) { @@ -3592,8 +3595,10 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { if (request.has("client_id")) { clientId = request.get("client_id").toString(); } + + String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint", ConfigManager.getEsignetBaseUrl()); jsonString = replaceKeywordWithValue(jsonString, "$CLIENT_ASSERTION_USER3_JWK$", - signJWKKey(clientId, oidcJWKKey3)); + signJWKKey(clientId, oidcJWKKey3, tempUrl)); } if (jsonString.contains("$CLIENT_ASSERTION_USER4_JWK$")) { @@ -3812,7 +3817,7 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { baseURL.replace("injicertify.", "injicertify-insurance.")); } } else { - tempUrl = getValueFromEsignetWellKnownEndPoint("issuer"); + tempUrl = getValueFromEsignetWellKnownEndPoint("issuer", ConfigManager.getEsignetBaseUrl()); if (tempUrl.contains("esignet.")) { tempUrl = tempUrl.replace("esignet.", propsKernel.getProperty("esignetMockBaseURL")); } @@ -3874,7 +3879,7 @@ public static String signJWKForMock(String clientId, String accessToken, RSAKey } public static String signJWK(String clientId, String accessToken, RSAKey jwkKey, String testCaseName) { - String tempUrl = getValueFromEsignetWellKnownEndPoint("issuer"); + String tempUrl = getValueFromEsignetWellKnownEndPoint("issuer", ConfigManager.getEsignetBaseUrl()); int idTokenExpirySecs = Integer .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); @@ -6005,7 +6010,7 @@ public static void createAndPublishPolicyForKyc() { // } public static String signJWKKeyForMock(String clientId, RSAKey jwkKey) { - String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint"); + String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint", ConfigManager.getEsignetBaseUrl()); if (tempUrl.contains("esignet.")) { tempUrl = tempUrl.replace("esignet.", propsKernel.getProperty("esignetMockBaseURL")); } @@ -6045,8 +6050,7 @@ public static String signJWKKeyForMock(String clientId, RSAKey jwkKey) { return clientAssertionToken; } - public static String signJWKKey(String clientId, RSAKey jwkKey) { - String tempUrl = getValueFromEsignetWellKnownEndPoint("token_endpoint"); + public static String signJWKKey(String clientId, RSAKey jwkKey, String tempUrl) { int idTokenExpirySecs = Integer .parseInt(getValueFromEsignetActuator(ConfigManager.getEsignetActuatorPropertySection(), GlobalConstants.MOSIP_ESIGNET_ID_TOKEN_EXPIRE_SECONDS)); @@ -6180,8 +6184,8 @@ public static int getOtpExpTimeFromActuator() { return Integer.parseInt(otpExpTime); } - public static String getValueFromEsignetWellKnownEndPoint(String key) { - String url = ConfigManager.getEsignetBaseUrl() + propsKernel.getProperty("esignetWellKnownEndPoint"); + public static String getValueFromEsignetWellKnownEndPoint(String key, String baseURL) { + String url = baseURL + propsKernel.getProperty("esignetWellKnownEndPoint"); Response response = null; JSONObject responseJson = null; if (responseJson == null) { @@ -6841,7 +6845,8 @@ public static String smtpOtpHandler(String inputJson, String testCaseName) { } } if (BaseTestCase.currentModule.equals(GlobalConstants.ESIGNET) - || testCaseName.startsWith("Mimoto_WalletBinding")) { + || testCaseName.startsWith("Mimoto_WalletBinding") + || testCaseName.startsWith("Mimoto_ESignet_AuthenticateUser")) { if (request.has(GlobalConstants.REQUEST)) { if (request.getJSONObject(GlobalConstants.REQUEST).has("otp")) { if (request.getJSONObject(GlobalConstants.REQUEST).getString("otp") diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java index 50d6c2fb0f..964ec364cf 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java @@ -426,7 +426,7 @@ public static void init() { if (System.getenv(ESIGNET_BASE_URL) != null) { eSignetbaseurl = System.getenv(ESIGNET_BASE_URL); } else { - eSignetbaseurl = System.getProperty("env.endpoint").replace("-internal", ""); + eSignetbaseurl = System.getProperty("env.endpoint").replace("api-internal", "esignet"); } propsKernel.setProperty(ESIGNET_BASE_URL, eSignetbaseurl); diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java index 7783523e2d..6d9ee59985 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java @@ -581,7 +581,7 @@ public static boolean doesResponseHasErrorCode(String responseString, String all public static void reportServerIssues(String responseString, TestCaseDTO testCaseDTO) throws AdminTestException { if (responseString.startsWith("") || responseString.startsWith(" Date: Wed, 4 Sep 2024 19:47:26 +0530 Subject: [PATCH 14/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 147432d08b..1da9a77487 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -7825,6 +7825,24 @@ else if (testCaseName.contains("_withinvaliddemofield_inupdate")) { } } } + //50 + if (testCaseName.contains("_withonedemofield")) { + if (identity.has("selectedHandles")) { + String firstHandle = selectedHandles.getString(0); + for (int j = 1; j < selectedHandles.length(); j++) { + if (identity.has(handle)) { + Object handleValue = identity.get(handle); + if (handleValue instanceof JSONArray) { + identity.remove(handle); + } + } + } + JSONArray newSelectedHandles = new JSONArray(); + newSelectedHandles.put(firstHandle); + identity.put("selectedHandles", newSelectedHandles); + } + } + @@ -8048,6 +8066,23 @@ else if (testCaseName.contains("_withoutselectedhandlesandattri")) { identity.remove("selectedHandles"); } + + else if (testCaseName.contains("_withalldemofieldsremoved")) { + + for (int j = 0; j < selectedHandles.length(); j++) { + if (identity.has(handle)) { + identity.remove(handle); + } + } + } + + else if (testCaseName.contains("_withemptyselectedhandle")) { + if (identity.has("selectedHandles")) { + identity.put("selectedHandles", new JSONArray()); + } + } + + @@ -8070,6 +8105,14 @@ else if (testCaseName.contains("_witharandomnonhandleattr")) { } } } + + else if (testCaseName.contains("_updateselectedhandleswithinvalid")) { + JSONArray updatedHandlesArray = new JSONArray(); + updatedHandlesArray.put("invalidscehema123"); + identity.put("selectedHandles", updatedHandlesArray); + } + + From 4f0400d4cd80bb3aed778191dd0cf1abed6bcda6 Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:01:37 +0530 Subject: [PATCH 15/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index d1b8b6660d..e755d38e6a 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -7831,7 +7831,7 @@ else if (testCaseName.contains("_withinvaliddemofield_inupdate")) { } } //50 - if (testCaseName.contains("_withonedemofield")) { + else if (testCaseName.contains("_withonedemofield")) { if (identity.has("selectedHandles")) { String firstHandle = selectedHandles.getString(0); for (int j = 1; j < selectedHandles.length(); j++) { @@ -7847,6 +7847,15 @@ else if (testCaseName.contains("_withinvaliddemofield_inupdate")) { identity.put("selectedHandles", newSelectedHandles); } } + + //82 + + else if (testCaseName.contains("_withcasesensitivehandles")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.put("value", "HANDLES"); + } + } @@ -7873,6 +7882,10 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String JSONObject request = jsonObj.getJSONObject("request"); JSONObject identity = request.getJSONObject("identity"); JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); + String phone = getValueFromAuthActuator("json-property", "phone_number"); + String result = phone.replaceAll("\\[\"|\"\\]", ""); + + // Iterate over each handle in the selectedHandles array for (int i = 0; i < selectedHandles.length(); i++) { @@ -7924,8 +7937,7 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String } } else if (testCaseName.contains("_withmultipledemohandles")) { // Handle specific demo handles by checking and adding them to the selectedHandles - String phone = getValueFromAuthActuator("json-property", "phone_number"); - String result = phone.replaceAll("\\[\"|\"\\]", ""); + boolean containsPhone = false; for (int j = 0; j < selectedHandles.length(); j++) { if (result.equalsIgnoreCase(selectedHandles.getString(j))) { @@ -8086,16 +8098,13 @@ else if (testCaseName.contains("_withemptyselectedhandle")) { identity.put("selectedHandles", new JSONArray()); } } - - - - + else if (testCaseName.contains("_witharandomnonhandleattr")) { if (identity.has("selectedHandles")) { List existingHandles = new ArrayList<>(); - for (int j = 0; i < selectedHandles.length(); i++) { - existingHandles.add(selectedHandles.getString(i)); + for (int j = 0; j < selectedHandles.length(); j++) { + existingHandles.add(selectedHandles.getString(j)); } Iterator keys = identity.keys(); while (keys.hasNext()) { @@ -8116,6 +8125,39 @@ else if (testCaseName.contains("_updateselectedhandleswithinvalid")) { updatedHandlesArray.put("invalidscehema123"); identity.put("selectedHandles", updatedHandlesArray); } + + else if (testCaseName.contains("_withinvaliddhandle")) { + selectedHandles.put("newFieldHandle"); + } + + else if (testCaseName.contains("_updateselectedhandleswithscehmaattrwhichisnothandle")) { + Iterator keys = identity.keys(); + while (keys.hasNext()) { + String key = keys.next(); + if (!selectedHandles.toList().contains(key) && identity.optString(key) != null && identity.get(key) instanceof String) { + selectedHandles.put(key); + break; + } + } + } + + else if (testCaseName.contains("_removeselectedhandle_updatephone")) { + if (identity.has("selectedHandles")) { + identity.remove("selectedHandles"); + } + + if (identity.has(result)) { + identity.put(result, generateRandomNumberString(10)); + } + } + + else if (testCaseName.contains("_withupdatedhandlewhichisnotinschema")) { + JSONArray newSelectedHandles = new JSONArray(); + newSelectedHandles.put("invalid12@@"); + identity.put("selectedHandles", newSelectedHandles); + } + + From e2d4feecb7165e5abbf91d1b16e45063f4cd30a4 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 10 Sep 2024 12:53:10 +0530 Subject: [PATCH 16/43] Reverted as per develop Signed-off-by: Mohanachandran S --- authentication-demo-service/pom.xml | 612 ++++++++++++---------------- pom.xml | 286 +++++++++++++ 2 files changed, 541 insertions(+), 357 deletions(-) create mode 100644 pom.xml diff --git a/authentication-demo-service/pom.xml b/authentication-demo-service/pom.xml index cc1db3e5d5..05769931c9 100644 --- a/authentication-demo-service/pom.xml +++ b/authentication-demo-service/pom.xml @@ -1,388 +1,286 @@ - - 4.0.0 + + + 4.0.0 - - io.mosip.testrig.authentication.demo - 1.2.1-java21-SNAPSHOT - authentication-demo-service - authentication-demo-service - Parent project of MOSIP functional tests - https://github.com/mosip/mosip-functional-tests - jar + io.mosip.testrig + mosip-functional-test + 1.2.1-SNAPSHOT + pom + mosip-function-test + Parent project of MOSIP Functional test + mosip-functional-tests - - - MPL 2.0 - https://www.mozilla.org/en-US/MPL/2.0/ - - + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + + - - scm:git:git://github.com/mosip/mosip-functional-tests.git - scm:git:ssh://github.com:mosip/mosip-functional-tests.git - https://github.com/mosip/mosip-functional-tests - HEAD - + + scm:git:git://github.com/mosip/mosip-functional-tests.git + scm:git:ssh://github.com:mosip/mosip-functional-tests.git + https://github.com/mosip/mosip-functional-tests + HEAD + - - - Mosip - mosip.emailnotifier@gmail.com - io.mosip - https://github.com/mosip/mosip-functional-tests - - - - - - local-maven-repo - file:///${basedir}/lib - - - - snapshots-repo - https://oss.sonatype.org/content/repositories/snapshots - false - true - + + + Mosip + mosip.emailnotifier@gmail.com + io.mosip + https://github.com/mosip/mosip-functional-tests + + + - releases-repo - https://oss.sonatype.org/service/local/staging/deploy/maven2 - false - false - - + ossrh + CentralRepository + https://oss.sonatype.org/content/repositories/snapshots + default + + true + + - danubetech-maven-public - https://repo.danubetech.com/repository/maven-public/ - - - - - - 3.2.3 - 2.0.0.RELEASE - - 21 - 21 - 3.8.0 - 3.0.1 - 2.9.2 - 0.8.2 - false - - 1.2.1-java21-SNAPSHOT - - - - + central + MavenCentral + default + https://repo1.maven.org/maven2 + + false + + + - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - - - commons-io - commons-io - - - javax.ws.rs - javax.ws.rs-api - 2.0 - - - org.glassfish.jersey.core - jersey-common - test - - - io.mosip.authentication - authentication-core - 1.2.1-java21-SNAPSHOT - - - commons-codec - commons-codec - - - - - commons-fileupload - commons-fileupload - + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - - org.mockito - mockito-core - + + UTF-8 - - io.mosip.kernel - kernel-core - 1.2.1-java21-SNAPSHOT - - - org.springframework.boot - - spring-boot-starter-security - - - - commons-codec - commons-codec - - - - - io.mosip.kernel - kernel-websubclient-api - ${kernel-websubclient-api.version} - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.apache.httpcomponents - httpclient - - - commons-codec - commons-codec - - - + + 21 + 21 + 3.7.0.1746 + 0.8.11 + 0.8.11 - - - org.apache.httpcomponents - httpmime - 4.5.7 - - - org.json - json - 20180813 - - - org.apache.commons - commons-lang3 - - - org.tensorflow - tensorflow - 1.12.0 - + + io.mosip.testrig.apirig.apitest.commons/** + + io.mosip.testrig.apirig.apitest.commons/** + - - org.apache.commons - commons-io - 1.3.2 - + + authentication-demo-service + - - - commons-lang - commons-lang - 2.4 - - - org.apache.commons - commons-text - 1.6 - - - commons-codec - commons-codec - - - io.mosip.kernel - kernel-templatemanager-velocity - 1.2.1-java21-SNAPSHOT - - - org.springframework.cloud - spring-cloud-starter-config - - - org.postgresql - postgresql - - - com.github.jai-imageio - jai-imageio-jpeg2000 - 1.3.0 - - - org.json - json - 20180813 - - - io.mosip.kernel - kernel-keymanager-service - 1.2.1-java21-SNAPSHOT - lib - - - org.springframework.boot - - spring-boot-starter-security - - - - - - dom4j - dom4j - 1.6.1 - - - org.junit.vintage - junit-vintage-engine - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.5.0 - - - - - - io.mosip.kernel - kernel-bom - 1.2.1-java21-SNAPSHOT - pom - import - - - - - - + + + org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - + maven-surefire-plugin - none + ${skipTests} + false + + --add-opens=java.base/java.lang=ALL-UNNAMED + --add-opens=java.base/java.util=ALL-UNNAMED + --add-opens=java.base/java.net=ALL-UNNAMED + --add-opens=java.base/java.time=ALL-UNNAMED + --add-opens=java.base/java.io=ALL-UNNAMED + --add-opens=java.base/java.time.format=ALL-UNNAMED + - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - true - ZIP - - - - - build-info - repackage - - - - - - + org.apache.maven.plugins - maven-gpg-plugin - 1.5 + maven-javadoc-plugin - sign-artifacts - verify - - sign + attach-javadocs + + jar - - - --pinentry-mode - loopback - - - - - pl.project13.maven - git-commit-id-plugin - 3.0.1 - - - get-the-git-infos - - revision - - validate - - - - true - ${project.build.outputDirectory}/service-git.properties - - ^git.build.(time|version)$ - ^git.commit.id.(abbrev|full)$ - - full - ${project.basedir}/.git - - - - + + none + -Xdoclint:none + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + https://oss.sonatype.org/ + false + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + true + + + ${project.name} + ${project.version} + + + + + + org.apache.maven.plugins + maven-war-plugin + + + + true + true + + + ${project.name} + ${project.version} + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + pl.project13.maven + git-commit-id-plugin + + + get-the-git-infos + + revision + + validate + + + + true + git.properties + + ^git.build.(time|version)$ + ^git.commit.id.(abbrev|full)$ + + full + ${project.basedir}/.git + + + + org.apache.maven.plugins maven-compiler-plugin - ${maven.compiler.version} + + -parameters + ${maven.compiler.source} ${maven.compiler.target} - - org.jacoco - jacoco-maven-plugin - - - **/constant/** - **/config/** - **/dto/** - io/mosip/authentication/service/*.class - io/mosip/authentication/service/filter/** - io/mosip/demo/authentication/service/IdAuthenticationDemoApplication.class - - - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - + + org.jacoco + jacoco-maven-plugin + ${jacoco.maven.plugin.version} + + + jacoco-initialize + + prepare-agent + + + + jacoco-site + package + + report + + + + + + + + + sonar + + . + src/main/java/**,src/main/resources/** + ${sonar.coverage.exclusions} + https://sonarcloud.io + true + + + false + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${maven.sonar.plugin.version} + + + verify + + sonar + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..306e1ddc56 --- /dev/null +++ b/pom.xml @@ -0,0 +1,286 @@ + + + 4.0.0 + + io.mosip.testrig + mosip-functional-test + 1.2.1-SNAPSHOT + pom + mosip-function-test + Parent project of MOSIP Functional test + mosip-functional-tests + + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + + + + + scm:git:git://github.com/mosip/mosip-functional-tests.git + scm:git:ssh://github.com:mosip/mosip-functional-tests.git + https://github.com/mosip/mosip-functional-tests + HEAD + + + + + Mosip + mosip.emailnotifier@gmail.com + io.mosip + https://github.com/mosip/mosip-functional-tests + + + + + + ossrh + CentralRepository + https://oss.sonatype.org/content/repositories/snapshots + default + + true + + + + central + MavenCentral + default + https://repo1.maven.org/maven2 + + false + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + UTF-8 + + + 21 + 21 + 3.7.0.1746 + 0.8.11 + 0.8.11 + + + io.mosip.testrig.apirig.apitest.commons/** + + io.mosip.testrig.apirig.apitest.commons/** + + + + authentication-demo-service + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${skipTests} + false + + --add-opens=java.base/java.lang=ALL-UNNAMED + --add-opens=java.base/java.util=ALL-UNNAMED + --add-opens=java.base/java.net=ALL-UNNAMED + --add-opens=java.base/java.time=ALL-UNNAMED + --add-opens=java.base/java.io=ALL-UNNAMED + --add-opens=java.base/java.time.format=ALL-UNNAMED + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + none + -Xdoclint:none + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + https://oss.sonatype.org/ + false + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + true + + + ${project.name} + ${project.version} + + + + + + org.apache.maven.plugins + maven-war-plugin + + + + true + true + + + ${project.name} + ${project.version} + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + pl.project13.maven + git-commit-id-plugin + + + get-the-git-infos + + revision + + validate + + + + true + git.properties + + ^git.build.(time|version)$ + ^git.commit.id.(abbrev|full)$ + + full + ${project.basedir}/.git + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -parameters + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.maven.plugin.version} + + + jacoco-initialize + + prepare-agent + + + + jacoco-site + package + + report + + + + + + + + + sonar + + . + src/main/java/**,src/main/resources/** + ${sonar.coverage.exclusions} + https://sonarcloud.io + true + + + false + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${maven.sonar.plugin.version} + + + verify + + sonar + + + + + + + + + From 4b60d99b96097fc974bf528872a98b8454555bf6 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 10 Sep 2024 12:59:30 +0530 Subject: [PATCH 17/43] Reverted as per develop Signed-off-by: Mohanachandran S --- .../dataprovider/BiometricDataProvider.java | 1 - authentication-demo-service/pom.xml | 615 ++++++++++-------- 2 files changed, 360 insertions(+), 256 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/dataprovider/BiometricDataProvider.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/dataprovider/BiometricDataProvider.java index adaca9effb..871201572c 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/dataprovider/BiometricDataProvider.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/dataprovider/BiometricDataProvider.java @@ -64,7 +64,6 @@ public class BiometricDataProvider { public static HashMap portmap = new HashMap(); - //private static final Logger logger = LoggerFactory.getLogger(BiometricDataProvider.class); private static final Logger logger = Logger.getLogger(BiometricDataProvider.class); // String constants diff --git a/authentication-demo-service/pom.xml b/authentication-demo-service/pom.xml index 05769931c9..89e048b3be 100644 --- a/authentication-demo-service/pom.xml +++ b/authentication-demo-service/pom.xml @@ -1,286 +1,391 @@ - - - 4.0.0 + + 4.0.0 - io.mosip.testrig - mosip-functional-test - 1.2.1-SNAPSHOT - pom - mosip-function-test - Parent project of MOSIP Functional test - mosip-functional-tests + + io.mosip.testrig.authentication.demo + 1.2.1-SNAPSHOT + authentication-demo-service + authentication-demo-service + Parent project of MOSIP functional tests + https://github.com/mosip/mosip-functional-tests + jar - - - MPL 2.0 - https://www.mozilla.org/en-US/MPL/2.0/ - - + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + + - - scm:git:git://github.com/mosip/mosip-functional-tests.git - scm:git:ssh://github.com:mosip/mosip-functional-tests.git - https://github.com/mosip/mosip-functional-tests - HEAD - + + scm:git:git://github.com/mosip/mosip-functional-tests.git + scm:git:ssh://github.com:mosip/mosip-functional-tests.git + https://github.com/mosip/mosip-functional-tests + HEAD + - - - Mosip - mosip.emailnotifier@gmail.com - io.mosip - https://github.com/mosip/mosip-functional-tests - - + + + Mosip + mosip.emailnotifier@gmail.com + io.mosip + https://github.com/mosip/mosip-functional-tests + + + + + + local-maven-repo + file:///${basedir}/lib + + + + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots + false + true + - - - ossrh - CentralRepository - https://oss.sonatype.org/content/repositories/snapshots - default - - true - - - central - MavenCentral - default - https://repo1.maven.org/maven2 - - false - - - + releases-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2 + false + false + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + danubetech-maven-public + https://repo.danubetech.com/repository/maven-public/ + + - - UTF-8 + + + 3.2.3 + 2.0.0.RELEASE + + 21 + 21 + 3.8.0 + 3.0.1 + 2.9.2 + 0.8.2 + + 1.2.1-SNAPSHOT + + - - 21 - 21 - 3.7.0.1746 - 0.8.11 - 0.8.11 + - - io.mosip.testrig.apirig.apitest.commons/** - - io.mosip.testrig.apirig.apitest.commons/** - + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + commons-io + commons-io + + + javax.ws.rs + javax.ws.rs-api + 2.0 + + + org.glassfish.jersey.core + jersey-common + test + + + io.mosip.authentication + authentication-core + 1.2.1-SNAPSHOT + + + commons-codec + commons-codec + + + org.springdoc + springdoc-openapi-ui + + + + + commons-fileupload + commons-fileupload + - - authentication-demo-service - + + org.mockito + mockito-core + - - - - org.apache.maven.plugins - maven-surefire-plugin - - ${skipTests} - false - - --add-opens=java.base/java.lang=ALL-UNNAMED - --add-opens=java.base/java.util=ALL-UNNAMED - --add-opens=java.base/java.net=ALL-UNNAMED - --add-opens=java.base/java.time=ALL-UNNAMED - --add-opens=java.base/java.io=ALL-UNNAMED - --add-opens=java.base/java.time.format=ALL-UNNAMED - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - none - -Xdoclint:none - - - - org.sonatype.plugins - nexus-staging-maven-plugin - true - - ossrh - https://oss.sonatype.org/ - false - - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - true - - - ${project.name} - ${project.version} - - - - - - org.apache.maven.plugins - maven-war-plugin - - - - true - true - - - ${project.name} - ${project.version} - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar-no-fork - - - - - + + io.mosip.kernel + kernel-core + 1.2.1-SNAPSHOT + + + org.springframework.boot + + spring-boot-starter-security + + + + commons-codec + commons-codec + + + + + io.mosip.kernel + kernel-websubclient-api + ${kernel-websubclient-api.version} + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.apache.httpcomponents + httpclient + + + commons-codec + commons-codec + + + + + + + org.apache.httpcomponents + httpmime + 4.5.7 + + + org.json + json + 20180813 + + + org.apache.commons + commons-lang3 + + + org.tensorflow + tensorflow + 1.12.0 + + + + org.apache.commons + commons-io + 1.3.2 + + + + + commons-lang + commons-lang + 2.4 + + + org.apache.commons + commons-text + 1.6 + + + commons-codec + commons-codec + + + io.mosip.kernel + kernel-templatemanager-velocity + 1.2.1-SNAPSHOT + + + org.springframework.cloud + spring-cloud-starter-config + + + org.postgresql + postgresql + + + com.github.jai-imageio + jai-imageio-jpeg2000 + 1.3.0 + + + org.json + json + 20180813 + + + io.mosip.kernel + kernel-keymanager-service + 1.2.1-SNAPSHOT + lib + + + org.springframework.boot + + spring-boot-starter-security + + + + + + dom4j + dom4j + 1.6.1 + + + org.junit.vintage + junit-vintage-engine + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + + + + io.mosip.kernel + kernel-bom + 1.2.1-SNAPSHOT + pom + import + + + + + + org.apache.maven.plugins - maven-gpg-plugin + maven-javadoc-plugin - sign-artifacts - verify + attach-javadocs - sign + jar - - - --pinentry-mode - loopback - - - - - - - pl.project13.maven - git-commit-id-plugin - - - get-the-git-infos - - revision - - validate - true - git.properties - - ^git.build.(time|version)$ - ^git.commit.id.(abbrev|full)$ - - full - ${project.basedir}/.git - + none - + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + true + ZIP + + + + + build-info + repackage + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + pl.project13.maven + git-commit-id-plugin + 3.0.1 + + + get-the-git-infos + + revision + + validate + + + + true + ${project.build.outputDirectory}/service-git.properties + + ^git.build.(time|version)$ + ^git.commit.id.(abbrev|full)$ + + full + ${project.basedir}/.git + + + + org.apache.maven.plugins maven-compiler-plugin + ${maven.compiler.version} - - -parameters - ${maven.compiler.source} ${maven.compiler.target} - - org.jacoco - jacoco-maven-plugin - ${jacoco.maven.plugin.version} - - - jacoco-initialize - - prepare-agent - - - - jacoco-site - package - - report - - - - - - - - - sonar - - . - src/main/java/**,src/main/resources/** - ${sonar.coverage.exclusions} - https://sonarcloud.io - true - - - false - - - - - org.sonarsource.scanner.maven - sonar-maven-plugin - ${maven.sonar.plugin.version} - - - verify - - sonar - - - - - - - - + + org.jacoco + jacoco-maven-plugin + + + **/constant/** + **/config/** + **/dto/** + io/mosip/authentication/service/*.class + io/mosip/authentication/service/filter/** + io/mosip/demo/authentication/service/IdAuthenticationDemoApplication.class + + + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + \ No newline at end of file From de66d51439502801ed62cb1c6f2f1d62bacf4fc8 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 10 Sep 2024 17:32:11 +0530 Subject: [PATCH 18/43] MOSIP-35671 - migrating to java21 Signed-off-by: Mohanachandran S --- .../main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index d1b8b6660d..bfc1b2800b 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -17,6 +17,7 @@ import java.io.OutputStreamWriter; import java.io.StringReader; import java.lang.reflect.Type; +import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; @@ -5724,7 +5725,7 @@ public static String generateHbsForPrereg(boolean isItUpdate) { org.json.JSONObject responseJson = new org.json.JSONObject(response.asString()); org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); - Double schemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + Double schemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); logger.info(schemaVersion); String schemaJsonData = schemaData.getString(GlobalConstants.SCHEMA_JSON); From 42433ad07c8dcd92c4d89c4a62cea14da35e4220 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 10 Sep 2024 17:45:30 +0530 Subject: [PATCH 19/43] MOSIP-35671 - migrating to java21 Signed-off-by: Mohanachandran S --- .github/workflows/apitest-push-trigger.yml | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/apitest-push-trigger.yml b/.github/workflows/apitest-push-trigger.yml index c3f27d1a17..d705909de1 100644 --- a/.github/workflows/apitest-push-trigger.yml +++ b/.github/workflows/apitest-push-trigger.yml @@ -56,6 +56,33 @@ jobs: uses: mosip/kattu/.github/workflows/maven-sonar-analysis.yml@master-java21 with: SERVICE_LOCATION: ./apitest-commons + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + ORG_KEY: ${{ secrets.ORG_KEY }} + OSSRH_USER: ${{ secrets.OSSRH_USER }} + OSSRH_SECRET: ${{ secrets.OSSRH_SECRET }} + OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} + GPG_SECRET: ${{ secrets.GPG_SECRET }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + build-maven-authentication-demo-service: + uses: mosip/kattu/.github/workflows/maven-build.yml@master-java21 + with: + SERVICE_LOCATION: ./authentication-demo-service + BUILD_ARTIFACT: authentication-demo-service + secrets: + OSSRH_USER: ${{ secrets.OSSRH_USER }} + OSSRH_SECRET: ${{ secrets.OSSRH_SECRET }} + OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} + GPG_SECRET: ${{ secrets.GPG_SECRET }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + sonar_analysis-authentication-demo-service: + needs: build-maven-authentication-demo-service + if: "${{ github.event_name != 'pull_request' }}" + uses: mosip/kattu/.github/workflows/maven-sonar-analysis.yml@master-java21 + with: + SERVICE_LOCATION: ./authentication-demo-service secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ORG_KEY: ${{ secrets.ORG_KEY }} From 381d838bffaa7e2707caa9e9f1f359629d5975ce Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:34:17 +0530 Subject: [PATCH 20/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 98 +++++++++++++++++-- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index e755d38e6a..4f87fb0327 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -256,6 +256,7 @@ public class AdminTestUtil extends BaseTestCase { public static final String BINDINGCERTCONSENTEMPTYCLAIMFILE = "BINDINGCERTCONSENTEMPTYCLAIMFile"; public static final String BINDINGCERTCONSENTUSER2FILE = "BINDINGCERTCONSENTUSER2File"; public static final String BINDINGCERTVIDCONSENTUSER2FILE = "BINDINGCERTCONSENTVIDUSER2File"; + private static String selectedHandlesValue=null; private static final String UIN_CODE_VERIFIER_POS_1 = generateRandomAlphaNumericString(GlobalConstants.INTEGER_36); @@ -7657,6 +7658,8 @@ public String replaceArrayHandleValues(String inputJson, String testCaseName) { JSONObject request = jsonObj.getJSONObject("request"); JSONObject identity = request.getJSONObject("identity"); JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); + String email = getValueFromAuthActuator("json-property", "emailId"); + String emailResult = email.replaceAll("\\[\"|\"\\]", ""); for (int i = 0; i < selectedHandles.length(); i++) { String handle = selectedHandles.getString(i); @@ -7856,9 +7859,69 @@ else if (testCaseName.contains("_withcasesensitivehandles")) { obj.put("value", "HANDLES"); } } - + //77 + else if (testCaseName.contains("_replaceselectedhandles")) { + identity.put("selectedHandles", new JSONArray().put(emailResult)); + } + //76 + else if (testCaseName.contains("_onlywithemail")) { + identity.put("selectedHandles", new JSONArray().put(emailResult)); + } + + //73 + else if (testCaseName.contains("_withoutselectedhandlesinidentity")) { + identity.remove("selectedHandles"); + } + else if (testCaseName.contains("_withdublicatevalue")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + if (testCaseName.contains("_save_withdublicatevalue")) + selectedHandlesValue=obj.getString("value"); + obj.put("value", selectedHandlesValue); + } + } + else if (testCaseName.contains("_withmultipledublicatevalue")) { + JSONObject secondValue = new JSONObject(); + secondValue.put("value", selectedHandlesValue); + secondValue.put("tags", new JSONArray().put("handle")); + handleArray.put(secondValue); + } + else if (testCaseName.contains("_removevalueaddexistingvalue")) { + for (int j = 0; j < handleArray.length(); j++) { + JSONObject obj = handleArray.getJSONObject(j); + obj.remove("value"); + obj.put("value", selectedHandlesValue); + } + } + else if (testCaseName.contains("_withselectedhandlephone")) { + if (identity.has("selectedHandles")) { + // Remove "email" and "functionalId", keep only "phone" + JSONArray updatedHandles = new JSONArray(); + boolean containsPhone = false; + + for (int j = 0; j < selectedHandles.length(); j++) { + handle = selectedHandles.getString(j); + if (handle.equalsIgnoreCase("phone")) { + containsPhone = true; + updatedHandles.put("phone"); // Ensure "phone" is kept + } + } + + // Add "phone" if not present + if (!containsPhone) { + updatedHandles.put("phone"); + } + // Update the identity with the modified selectedHandles array + identity.put("selectedHandles", updatedHandles); + } else { + // If "selectedHandles" doesn't exist, create it with "phone" + JSONArray newSelectedHandles = new JSONArray(); + newSelectedHandles.put("phone"); + identity.put("selectedHandles", newSelectedHandles); + } + } else { @@ -7884,6 +7947,8 @@ public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String JSONArray selectedHandles = identity.getJSONArray("selectedHandles"); String phone = getValueFromAuthActuator("json-property", "phone_number"); String result = phone.replaceAll("\\[\"|\"\\]", ""); + String email = getValueFromAuthActuator("json-property", "emailId"); + String emailResult = email.replaceAll("\\[\"|\"\\]", ""); @@ -8156,15 +8221,28 @@ else if (testCaseName.contains("_withupdatedhandlewhichisnotinschema")) { newSelectedHandles.put("invalid12@@"); identity.put("selectedHandles", newSelectedHandles); } - - - - - - - - - + + else if (testCaseName.contains("_replaceselectedhandles")) { + identity.put("selectedHandles", new JSONArray().put(result)); + } + + else if (testCaseName.contains("_updatewithphoneemail")) { + JSONArray updatedHandles = new JSONArray(); + updatedHandles.put(emailResult); + updatedHandles.put(result); + + identity.put("selectedHandles", updatedHandles); + } + else if (testCaseName.contains("_withusedphone")) { + if (identity.has(result)) { + identity.put(result, "$ID:AddIdentity_array_handle_value_smoke_Pos_withphonenumber_PHONE$" ); + } + } + else if (testCaseName.contains("_withphonevalue")) { + if (identity.has(result)) { + identity.put(result, "$ID:AddIdentity_array_handle_value_smoke_Pos_withselectedhandlephone_PHONE$" ); + } + } identity.put(handle, handleArray); } From 2ef79764fa1d091dc950679174a6c560e0baab3f Mon Sep 17 00:00:00 2001 From: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:35:40 +0530 Subject: [PATCH 21/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 4f87fb0327..cab9c7e6b9 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -5166,7 +5166,7 @@ else if (eachRequiredProp.equals("password")) { } } } - if (selectedHandles != null) { + if (selectedHandles != null && selectedHandles.size()>=1) { setfoundHandlesInIdSchema(true); identityJson.put("selectedHandles", selectedHandles); } From bd02d8583471e12158911fcb7c34fd20b5e100bf Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Wed, 11 Sep 2024 15:16:23 +0530 Subject: [PATCH 22/43] MOSIP-35700 Signed-off-by: Nandhukumar --- .../testrig/apirig/utils/AdminTestUtil.java | 47 +++++-------------- .../apirig/utils/OutputValidationUtil.java | 3 +- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index d1b8b6660d..b54d22c04e 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -1,8 +1,7 @@ package io.mosip.testrig.apirig.utils; import static io.restassured.RestAssured.given; -import de.mkammerer.argon2.Argon2; -import de.mkammerer.argon2.Argon2Factory; + import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -33,8 +32,6 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDate; @@ -56,6 +53,8 @@ import java.util.Set; import java.util.TimeZone; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.ws.rs.core.MediaType; @@ -79,9 +78,7 @@ import org.testng.SkipException; import org.yaml.snakeyaml.Yaml; -import java.lang.Double; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.jknack.handlebars.Context; import com.github.jknack.handlebars.Handlebars; @@ -119,8 +116,6 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.Response; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * @author Ravi Kant @@ -2737,9 +2732,8 @@ public String getAutogenIdKeyName(String testCaseName, String fieldName) { return null; int indexof = testCaseName.indexOf("_"); String autogenIdKeyName = testCaseName.substring(indexof + 1); - if ((!BaseTestCase.isTargetEnvLTS() || isOTPEnabled().equals("false")) && fieldName.equals("VID") - && (BaseTestCase.currentModule.equals("auth") || BaseTestCase.currentModule.equals("esignet") - || BaseTestCase.currentModule.equals(GlobalConstants.MIMOTO))) + if ((!BaseTestCase.isTargetEnvLTS()) && fieldName.equals("VID") + && (BaseTestCase.currentModule.equals("auth") || BaseTestCase.currentModule.equals("esignet"))) autogenIdKeyName = autogenIdKeyName + "_" + fieldName.toLowerCase(); else autogenIdKeyName = autogenIdKeyName + "_" + fieldName; @@ -3397,10 +3391,6 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { jsonString = replaceKeywordWithValue(jsonString, "$DOB$", getValueFromActuator(GlobalConstants.RESIDENT_DEFAULT_PROPERTIES, "mosip.date-of-birth.pattern")); } - if (jsonString.contains("$GETCLIENTIDFROMMIMOTOACTUATOR$")) { - jsonString = replaceKeywordWithValue(jsonString, "$GETCLIENTIDFROMMIMOTOACTUATOR$", - getValueFromMimotoActuator("overrides", "mimoto.oidc.partner.clientid")); - } if (jsonString.contains("$IDPREDIRECTURI$")) { jsonString = replaceKeywordWithValue(jsonString, "$IDPREDIRECTURI$", ApplnURI.replace(GlobalConstants.API_INTERNAL, "healthservices") + "/userprofile"); @@ -5165,7 +5155,7 @@ else if (eachRequiredProp.equals("password")) { } } } - if (selectedHandles != null) { + if (selectedHandles != null && selectedHandles.size()>=1) { setfoundHandlesInIdSchema(true); identityJson.put("selectedHandles", selectedHandles); } @@ -6538,17 +6528,16 @@ public static String getValueFromActuator(String section, String key) { } public static JSONArray mimotoActuatorResponseArray = null; - private static String otpEnabled = "true"; - - public static String isOTPEnabled() { - String value = getValueFromMimotoActuator("/mimoto-default.properties", "mosip.otp.download.enable"); - if (value != null && !(value.isBlank())) - otpEnabled = value; - return otpEnabled; - } public static String getValueFromMimotoActuator(String section, String key) { String url = ApplnURI + propsKernel.getProperty("actuatorMimotoEndpoint"); + if (!(System.getenv("useOldContextURL") == null) + && !(System.getenv("useOldContextURL").isBlank()) + && System.getenv("useOldContextURL").equalsIgnoreCase("true")) { + if (url.contains("/v1/mimoto/")) { + url = url.replace("/v1/mimoto/", "/residentmobileapp/"); + } + } String actuatorCacheKey = url + section + key; String value = actuatorValueCache.get(actuatorCacheKey); if (value != null && !value.isEmpty()) @@ -6846,16 +6835,6 @@ else if (testCaseName.startsWith("IdRepository_") && testCaseName.contains("Inva && (!isElementPresent(new JSONArray(schemaRequiredField), individualBiometrics))) { throw new SkipException(GlobalConstants.FEATURE_NOT_SUPPORTED_MESSAGE); } - } else if (BaseTestCase.currentModule.equalsIgnoreCase(GlobalConstants.MIMOTO)) { - if (isOTPEnabled().equals("false") && (testCaseDTO.getEndPoint().contains(GlobalConstants.SEND_OTP_ENDPOINT) - || testCaseDTO.getInput().contains(GlobalConstants.SEND_OTP_ENDPOINT) - || testCaseName.startsWith(GlobalConstants.MIMOTO_CREDENTIAL_STATUS))) { - throw new SkipException(GlobalConstants.OTP_FEATURE_NOT_SUPPORTED); - } else if (isOTPEnabled().equals("true") - && testCaseDTO.getEndPoint().contains(GlobalConstants.CREATE_VID_ENDPOINT)) { - throw new SkipException( - GlobalConstants.VID_GENERATED_USING_RESIDENT_API_SO_FEATURE_NOT_SUPPORTED_OR_NEEDED_MESSAGE); - } } if ((ConfigManager.isInServiceNotDeployedList(GlobalConstants.ESIGNET)) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java index 6d9ee59985..82d3f6b839 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/OutputValidationUtil.java @@ -581,7 +581,8 @@ public static boolean doesResponseHasErrorCode(String responseString, String all public static void reportServerIssues(String responseString, TestCaseDTO testCaseDTO) throws AdminTestException { if (responseString.startsWith("") || responseString.startsWith(" Date: Wed, 11 Sep 2024 16:04:16 +0530 Subject: [PATCH 23/43] MOSIP-35390 Signed-off-by: Sohan Kumar Dey <72375959+Sohandey@users.noreply.github.com> --- .../testrig/apirig/utils/AdminTestUtil.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index cab9c7e6b9..fcee1a42dc 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -5166,7 +5166,7 @@ else if (eachRequiredProp.equals("password")) { } } } - if (selectedHandles != null && selectedHandles.size()>=1) { + if (selectedHandles != null) { setfoundHandlesInIdSchema(true); identityJson.put("selectedHandles", selectedHandles); } @@ -7915,7 +7915,11 @@ else if (testCaseName.contains("_withselectedhandlephone")) { // Update the identity with the modified selectedHandles array identity.put("selectedHandles", updatedHandles); - } else { + }else if (testCaseName.contains("_removealltagshandles")) { + removeTagsHandles(jsonObj); + + + } else { // If "selectedHandles" doesn't exist, create it with "phone" JSONArray newSelectedHandles = new JSONArray(); newSelectedHandles.put("phone"); @@ -7938,7 +7942,29 @@ else if (testCaseName.contains("_withselectedhandlephone")) { return jsonObj.toString(); } - + private void removeTagsHandles(JSONObject jsonObj) { + for (String key : jsonObj.keySet()) { + Object value = jsonObj.get(key); + if (value instanceof JSONObject) { + JSONObject nestedObject = (JSONObject) value; + if (nestedObject.has("tags")) { + JSONArray tagsArray = nestedObject.getJSONArray("tags"); + if (tagsArray.length() == 1 && "handles".equals(tagsArray.getString(0))) { + nestedObject.remove("tags"); + } + } + removeTagsHandles(nestedObject); // Recursively call for deeper levels + } else if (value instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) value; + for (int i = 0; i < jsonArray.length(); i++) { + Object arrayElement = jsonArray.get(i); + if (arrayElement instanceof JSONObject) { + removeTagsHandles((JSONObject) arrayElement); // Recursively handle each element + } + } + } + } + } public String replaceArrayHandleValuesForUpdateIdentity(String inputJson, String testCaseName) { JSONObject jsonObj = new JSONObject(inputJson); @@ -8243,6 +8269,14 @@ else if (testCaseName.contains("_withphonevalue")) { identity.put(result, "$ID:AddIdentity_array_handle_value_smoke_Pos_withselectedhandlephone_PHONE$" ); } } + else if (testCaseName.contains("_removeselectedhandlesandupdateemail")) { + if (identity.has("selectedHandles")) { + identity.remove("selectedHandles"); + } + if (identity.has(emailResult)) { + identity.put(emailResult, "$ID:AddIdentity_array_handle_value_update_smoke_Pos_withselectedhandlephone_EMAIL$" ); + } + } identity.put(handle, handleArray); } From 239d982c0c3496b800358905d84923e71fb2afc4 Mon Sep 17 00:00:00 2001 From: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:48:52 +0530 Subject: [PATCH 24/43] MOSIP-35671 -Merging java21 with develop Signed-off-by: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> --- .../main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 0df91360db..9f841b83bc 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -4989,7 +4989,7 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { org.json.JSONObject responseJson = new org.json.JSONObject(response.asString()); org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); - Double schemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + Double schemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); idSchemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); String schemaJsonData = schemaData.getString(GlobalConstants.SCHEMA_JSON); From 99baa1538ba66ed0e4bf6ae8692a7ac2dea88ca0 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Wed, 11 Sep 2024 19:28:33 +0530 Subject: [PATCH 25/43] MOSIP-35671 -Merging java21 with develop Signed-off-by: Mohanachandran S --- .../io/mosip/testrig/apirig/utils/AdminTestUtil.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 9f841b83bc..55724ea6b1 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -4990,7 +4990,7 @@ public static String modifySchemaGenerateHbs(boolean regenerateHbs) { org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); Double schemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); - idSchemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + idSchemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); String schemaJsonData = schemaData.getString(GlobalConstants.SCHEMA_JSON); String schemaFile = schemaJsonData; @@ -5213,8 +5213,8 @@ public static String updateIdentityHbs(boolean regenerateHbs) { org.json.JSONObject responseJson = new org.json.JSONObject(response.asString()); org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); - Double schemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); - idSchemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + Double schemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); + idSchemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); String schemaJsonData = schemaData.getString(GlobalConstants.SCHEMA_JSON); String schemaFile = schemaJsonData; @@ -5403,7 +5403,7 @@ public static int generateLatestSchemaVersion() { org.json.JSONObject responseJson = new org.json.JSONObject(response.asString()); org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); - Double schemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + Double schemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); int latestSchemaVersion = Double.valueOf(schemaVersion).intValue(); logger.info(latestSchemaVersion); return latestSchemaVersion; @@ -5426,7 +5426,7 @@ public static String generateHbsForUpdateDraft() { org.json.JSONObject responseJson = new org.json.JSONObject(response.asString()); org.json.JSONObject schemaData = (org.json.JSONObject) responseJson.get(GlobalConstants.RESPONSE); - Double schemaVersion = (Double) schemaData.get(GlobalConstants.ID_VERSION); + Double schemaVersion = ((BigDecimal) schemaData.get(GlobalConstants.ID_VERSION)).doubleValue(); logger.info(schemaVersion); String schemaJsonData = schemaData.getString(GlobalConstants.SCHEMA_JSON); From eaeab05008c25c66fdebf606bfdd0c7bc5aaab43 Mon Sep 17 00:00:00 2001 From: Mohan E Date: Thu, 12 Sep 2024 14:11:48 +0530 Subject: [PATCH 26/43] [MOSIP-35421] Moved helm charts and installtion scripts of apitestrig and uitestrig. Signed-off-by: Mohanraj209 --- .github/workflows/chart-lint-publish.yml | 62 ++ deploy/apitestrig/README.md | 44 ++ deploy/apitestrig/copy_cm.sh | 25 + deploy/apitestrig/copy_secrets.sh | 24 + deploy/apitestrig/delete.sh | 30 + deploy/apitestrig/images/apitestrig-1.png | Bin 0 -> 59628 bytes deploy/apitestrig/images/apitestrig-2.png | Bin 0 -> 71183 bytes deploy/apitestrig/install.sh | 124 ++++ deploy/apitestrig/values.yaml | 17 + deploy/uitestrig/README.md | 32 + deploy/uitestrig/copy_cm.sh | 25 + deploy/uitestrig/copy_secrets.sh | 24 + deploy/uitestrig/delete.sh | 30 + deploy/uitestrig/install.sh | 90 +++ helm/apitestrig/.gitignore | 1 + helm/apitestrig/.helmignore | 21 + helm/apitestrig/Chart.yaml | 19 + helm/apitestrig/README.md | 10 + helm/apitestrig/templates/NOTES.txt | 1 + helm/apitestrig/templates/_helpers.tpl | 63 ++ helm/apitestrig/templates/clusterrole.yaml | 10 + .../templates/clusterrolebinding.yaml | 19 + helm/apitestrig/templates/configmaps.yaml | 21 + helm/apitestrig/templates/cronjob.yaml | 108 ++++ helm/apitestrig/templates/extra-list.yaml | 4 + helm/apitestrig/templates/secrets.yaml | 22 + .../apitestrig/templates/service-account.yaml | 12 + helm/apitestrig/values.yaml | 545 ++++++++++++++++++ helm/uitestrig/.helmignore | 21 + helm/uitestrig/Chart.yaml | 20 + helm/uitestrig/README.md | 10 + helm/uitestrig/templates/NOTES.txt | 1 + helm/uitestrig/templates/_helpers.tpl | 63 ++ helm/uitestrig/templates/clusterrole.yaml | 10 + .../templates/clusterrolebinding.yaml | 19 + helm/uitestrig/templates/configmaps.yaml | 21 + helm/uitestrig/templates/cronjob.yaml | 106 ++++ helm/uitestrig/templates/extra-list.yaml | 4 + helm/uitestrig/templates/secrets.yaml | 21 + helm/uitestrig/templates/service-account.yaml | 12 + helm/uitestrig/values.yaml | 511 ++++++++++++++++ 41 files changed, 2202 insertions(+) create mode 100644 .github/workflows/chart-lint-publish.yml create mode 100644 deploy/apitestrig/README.md create mode 100644 deploy/apitestrig/copy_cm.sh create mode 100644 deploy/apitestrig/copy_secrets.sh create mode 100644 deploy/apitestrig/delete.sh create mode 100644 deploy/apitestrig/images/apitestrig-1.png create mode 100644 deploy/apitestrig/images/apitestrig-2.png create mode 100644 deploy/apitestrig/install.sh create mode 100644 deploy/apitestrig/values.yaml create mode 100644 deploy/uitestrig/README.md create mode 100644 deploy/uitestrig/copy_cm.sh create mode 100644 deploy/uitestrig/copy_secrets.sh create mode 100644 deploy/uitestrig/delete.sh create mode 100644 deploy/uitestrig/install.sh create mode 100644 helm/apitestrig/.gitignore create mode 100644 helm/apitestrig/.helmignore create mode 100644 helm/apitestrig/Chart.yaml create mode 100644 helm/apitestrig/README.md create mode 100644 helm/apitestrig/templates/NOTES.txt create mode 100644 helm/apitestrig/templates/_helpers.tpl create mode 100644 helm/apitestrig/templates/clusterrole.yaml create mode 100644 helm/apitestrig/templates/clusterrolebinding.yaml create mode 100644 helm/apitestrig/templates/configmaps.yaml create mode 100644 helm/apitestrig/templates/cronjob.yaml create mode 100644 helm/apitestrig/templates/extra-list.yaml create mode 100644 helm/apitestrig/templates/secrets.yaml create mode 100644 helm/apitestrig/templates/service-account.yaml create mode 100644 helm/apitestrig/values.yaml create mode 100644 helm/uitestrig/.helmignore create mode 100644 helm/uitestrig/Chart.yaml create mode 100644 helm/uitestrig/README.md create mode 100644 helm/uitestrig/templates/NOTES.txt create mode 100644 helm/uitestrig/templates/_helpers.tpl create mode 100644 helm/uitestrig/templates/clusterrole.yaml create mode 100644 helm/uitestrig/templates/clusterrolebinding.yaml create mode 100644 helm/uitestrig/templates/configmaps.yaml create mode 100644 helm/uitestrig/templates/cronjob.yaml create mode 100644 helm/uitestrig/templates/extra-list.yaml create mode 100644 helm/uitestrig/templates/secrets.yaml create mode 100644 helm/uitestrig/templates/service-account.yaml create mode 100644 helm/uitestrig/values.yaml diff --git a/.github/workflows/chart-lint-publish.yml b/.github/workflows/chart-lint-publish.yml new file mode 100644 index 0000000000..1b51dddc74 --- /dev/null +++ b/.github/workflows/chart-lint-publish.yml @@ -0,0 +1,62 @@ +name: Validate / Publish helm charts + +on: + release: + types: [published] + pull_request: + types: [opened, reopened, synchronize] + paths: + - 'helm/**' + workflow_dispatch: + inputs: + IGNORE_CHARTS: + description: 'Provide list of charts to be ignored separated by pipe(|)' + required: false + default: '""' + type: string + CHART_PUBLISH: + description: 'Chart publishing to gh-pages branch' + required: false + default: 'NO' + type: string + options: + - YES + - NO + INCLUDE_ALL_CHARTS: + description: 'Include all charts for Linting/Publishing (YES/NO)' + required: false + default: 'NO' + type: string + options: + - YES + - NO + push: + branches: + - '!release-branch' + - '!master' + - 1.* + - 0.* + - develop + - release* + paths: + - 'helm/**' + +jobs: + chart-lint-publish: + uses: mosip/kattu/.github/workflows/chart-lint-publish.yml@master + with: + CHARTS_DIR: ./helm + CHARTS_URL: https://mosip.github.io/mosip-helm + REPOSITORY: mosip-helm + BRANCH: gh-pages + INCLUDE_ALL_CHARTS: "${{ inputs.INCLUDE_ALL_CHARTS || 'NO' }}" + IGNORE_CHARTS: "${{ inputs.IGNORE_CHARTS || '\"\"' }}" + CHART_PUBLISH: "${{ inputs.CHART_PUBLISH || 'YES' }}" + LINTING_CHART_SCHEMA_YAML_URL: "https://raw.githubusercontent.com/mosip/kattu/master/.github/helm-lint-configs/chart-schema.yaml" + LINTING_LINTCONF_YAML_URL: "https://raw.githubusercontent.com/mosip/kattu/master/.github/helm-lint-configs/lintconf.yaml" + LINTING_CHART_TESTING_CONFIG_YAML_URL: "https://raw.githubusercontent.com/mosip/kattu/master/.github/helm-lint-configs/chart-testing-config.yaml" + LINTING_HEALTH_CHECK_SCHEMA_YAML_URL: "https://raw.githubusercontent.com/mosip/kattu/master/.github/helm-lint-configs/health-check-schema.yaml" + DEPENDENCIES: "mosip,https://mosip.github.io/mosip-helm;" + secrets: + TOKEN: ${{ secrets.ACTION_PAT }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} \ No newline at end of file diff --git a/deploy/apitestrig/README.md b/deploy/apitestrig/README.md new file mode 100644 index 0000000000..9942e3cc69 --- /dev/null +++ b/deploy/apitestrig/README.md @@ -0,0 +1,44 @@ +# APITESTRIG + +## Introduction +ApiTestRig will test the working of APIs of the MOSIP modules. + +## Install +* Review `values.yaml` and, Make sure to enable required modules for apitestrig operation. +* Install +```sh +./install.sh +``` +* During the execution of the `install.sh` script, a prompt appears requesting information regarding the presence of a public domain and a valid SSL certificate on the server. +* If the server lacks a public domain and a valid SSL certificate, it is advisable to select the `n` option. Opting it will enable the `init-container` with an `emptyDir` volume and include it in the deployment process. +* The init-container will proceed to download the server's self-signed SSL certificate and mount it to the specified location within the container's Java keystore (i.e., `cacerts`) file. +* This particular functionality caters to scenarios where the script needs to be employed on a server utilizing self-signed SSL certificates. + +## Uninstall +* To uninstall ApiTestRig, run `delete.sh` script. +```sh +./delete.sh +``` + +## Run apitestrig manually + +#### Rancher UI +* Run apitestrig manually via Rancher UI. + ![apitestrig-2.png](./images/apitestrig-2.png) +* There are two modes of apitestrig `smoke` & `smokeAndRegression`. +* By default, apitestrig will execute with `smokeAndRegression`.
+ If you want to run apitestrig with only `smoke`.
+ You have to update the `apitestrig` configmap and rerun the specific apitestrig job. + +#### CLI +* Download Kubernetes cluster `kubeconfig` file from `rancher dashboard` to your local. + ![apitestrig-1.png](./images/apitestrig-1.png) +* Install `kubectl` package to your local machine. +* Run apitestrig manually via CLI by creating a new job from an existing k8s cronjob. + ``` + kubectl --kubeconfig= -n apitestrig create job --from=cronjob/ + ``` + example: + ``` + kubectl --kubeconfig=/home/xxx/Downloads/qa4.config -n apitestrig create job --from=cronjob/cronjob-apitestrig-masterdata cronjob-apitestrig-masterdata + ``` diff --git a/deploy/apitestrig/copy_cm.sh b/deploy/apitestrig/copy_cm.sh new file mode 100644 index 0000000000..450e385ca0 --- /dev/null +++ b/deploy/apitestrig/copy_cm.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copy configmaps from other namespaces +# DST_NS: Destination namespace + +function copying_cm() { + UTIL_URL=https://raw.githubusercontent.com/mosip/mosip-infra/master/deployment/v3/utils/copy_cm_func.sh + COPY_UTIL=./copy_cm_func.sh + DST_NS=apitestrig + + wget -q $UTIL_URL -O copy_cm_func.sh && chmod +x copy_cm_func.sh + + $COPY_UTIL configmap global default $DST_NS + $COPY_UTIL configmap keycloak-host keycloak $DST_NS + $COPY_UTIL configmap artifactory-share artifactory $DST_NS + $COPY_UTIL configmap config-server-share config-server $DST_NS + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_cm # calling function \ No newline at end of file diff --git a/deploy/apitestrig/copy_secrets.sh b/deploy/apitestrig/copy_secrets.sh new file mode 100644 index 0000000000..f0948b5f6d --- /dev/null +++ b/deploy/apitestrig/copy_secrets.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copy secrets from other namespaces +# DST_NS: Destination namespace + +function copying_secrets() { + UTIL_URL=https://raw.githubusercontent.com/mosip/mosip-infra/master/deployment/v3/utils/copy_cm_func.sh + COPY_UTIL=./copy_cm_func.sh + DST_NS=apitestrig + + wget -q $UTIL_URL -O copy_cm_func.sh && chmod +x copy_cm_func.sh + + $COPY_UTIL secret keycloak-client-secrets keycloak $DST_NS + $COPY_UTIL secret s3 s3 $DST_NS + $COPY_UTIL secret postgres-postgresql postgres $DST_NS + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_secrets # calling function \ No newline at end of file diff --git a/deploy/apitestrig/delete.sh b/deploy/apitestrig/delete.sh new file mode 100644 index 0000000000..6a28aa852f --- /dev/null +++ b/deploy/apitestrig/delete.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Uninstalls apitestrig +## Usage: ./delete.sh [kubeconfig] + +if [ $# -ge 1 ] ; then + export KUBECONFIG=$1 +fi + +function deleting_apitestrig() { + NS=apitestrig + while true; do + read -p "Are you sure you want to delete apitestrig helm charts?(Y/n) " yn + if [ $yn = "Y" ] + then + helm -n $NS delete apitestrig + break + else + break + fi + done + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +deleting_apitestrig # calling function \ No newline at end of file diff --git a/deploy/apitestrig/images/apitestrig-1.png b/deploy/apitestrig/images/apitestrig-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f36f6f0577259996c06017c7dbefd8365e26d3 GIT binary patch literal 59628 zcmbUIV{~NS7c~l3*hz(V)J?EMyL{?e^;WOrE004j>CMqZo06_8s0AM~a;2-~-?fS)ie8F0as@VYm zh`oP5UL4c$#eF}?&m^tOQ-ZluP;cpv0L-=KPw`EWJBFuLkyq_&&cY)Qk?zxC_KT`kkXjEHU;JRS8{(gQs0)fxy9L|I}aan@{oI%YNBC; zuz_X8wC<01#~**If9BW2^y7&yFUR3SJ~}crs%@?_;L8vBO^$_y1q7yzj)qT1Lj7%~ z4!LGAyvN7i8Uch@cIbjLb@IjE!$|URVE%TsLz4~5kLP<(j|+Pcfmkd)<#o9s^#}tz z+@C~8e8VqNGsGcie1H3X`8OEO>CH!~MG-4h) zJ$zhJQc_kW<=M{t-?1FV6^YKg_lnV3nf{7>#>nrLh{Ar+ zNRE?4ZF`g-we<~WIXBu0qrseRQ%N3gV>(TOW863`mCU0DucC0*Q%gDaQao09rNQeO zHQ%3n_Wg8IcL5x(INTBVTN6J_y9_az7l-i#RT>*#N`Wx={f0x%^R*?IU}s&B5T|8p zJ7pG{&VU{B44a#_8jW(Ey+>|kP5m#*E+T50b==t8@KXkU&F(Awf3rdH%NJs?FG5+L ze*jWd-c#0_QpDen5tkW%cD`6Jc6F@7lzP<`J`6~^hflB>0|16~M-O@~X>Sj0!tb{_ z&k<=%8jdEp2BUz@eMwk%%InXaD7X|A+-5f&3fvy+ohPL*&LFE458+GylR`8ow}cQi zp^s{d*w~eiUsmoE;DF95yM+x21qT9ZiQe64LO<$Wo8Bnk8eMS;$JHBb?xyp1+#s7^ z!)o2oEV8n?`K{i#2K#=uA?B{*J#6KIAi$5kL^W(|WYp_TKq+9wbjdjjHw#Ft$NJLf z0PkedlIL2IAn-!RRsZ_i)<`;eVv>f3@?Sd9*W^;B{l51T{fB#BjAhbtvW>?9a9&j4P6;t1L{;+=e8I4T~Wt5u&1_Osn=WIHU!}wOm?f++Od{KhMH|yg8SQ695i<w26hWvGwpk+o>N=LdD03-)Rnm$wYSwB z?HLI_P1KWgtILuM7N7IeE=oe*DLDwN%Q;P!M+qD(?Bw^ew0#sFV2zCO(y&qMqFV!e z;XfP^n#Pt(xc5^Tf9~g?q4672nM-f*_`+;()Q|?7y8W_ah{I*6T;96|L|hx0i@&Yc z48a#AEdc`nDA=L&8e%Uf^j*rJL$69w)#9uTS6W8A>?q7WbwS{dx}IdRXF@2ESd&DR zHr~8BZXL&9e6xx`4Q1YS{Cvp5p{zQc0f-`?h+6lY$ew`~U$SW))7Lip}Q#jRVzViN;aItsKbV&{t z5NdfOW#X;oAi_Dw0Ie}xkbQmd9*hmT+9QrsiUx(i=4s9q5xqq5JfwWuc6qm?)N{>- zm8E^<(WSOo!A)fDe6ie6da!x)G#%S-o$}7cSu!+4hsybO(WvEy%+j#g{*ZHt!&pnc z@~Hic<(y4A_>l`NFE9UxyOyK=ZZ2efEO5@_8maBZYa_xVv0|?j`xpV!;v+)VK3d~mVCY+o9XddlB2M;b`m*qR z4zg_t7}>i!14(iZT+E1(p1+rPW!@N1db2cQQl9N!r=P|9PbW>{`QF~(L!g% z?N|oRIN6wX*^zZl*4%J>f069AiXhDBPjU)p7GLpN!=K50EPB_L4tvW?Pd!(gz3=`Z zz$3+4Q@`kytqKd{aH}0jFmJINCdM@n6ECUV*!)gjB@~4PK(tPCUtNsSUTIJz*CSiw z<@botpd_bvaAge@gv_d~!P(y5c*n;fw^eZip>0czVvaR!P?y)bRG>K=NkCLt-gQ#L z;igCFHvtT_;?n?MPD%dXPePaw0PN3g?d;>U--W&cWJ{qN_r;IGE@}YOa5tS}QX9XV zy-ui)H1jV8=W^XZ*VmP&FA{V9UdDaf5PJBg+TNr~pWp1PyJ? z)uU&b6NA*oVzpai^{eE^74IeaGh8L2W;@ivQ$TttKU>`zbO9p$PI7+YB|{3#tuqek`P9c!$%(ars4#Wkr0wd-;$Fg^E-M&>(cIB zinTs@h{#`-A}5@`kR!rx=!Qt0u33k%h_vgFk%$)Q_@<{L!VltAW?Vak-i<)`E8f+{u(w?<7&H*@J+!@`s|z)D5My3P)f~b zOU#d0&T10o)_p|rXJ&!J>rMja&#R2d!zrg zH#0$qRT=_NHIWrd0dBz(G}34Ux6T$_i3Vq}>I*GnZC(oHi$6zSh?}XwG{a_l>2&g3 zD}!jM?SL=%c1+Np?#?~Z=KMk`fq6u9P>G03@8!mru)h>TL-rl2;63~sOvU7SXGM~f z`6&(Sh3j3Zb|+?cRr)^+m8X)x783{OUKi$3#BZ!YQ5u<9n(}>mriPa7$tZn z87wbn?MpoXfhyNV|8ahb@qj(M0JxGlJ3Qb=yxf->;Iw(t&PtM{=sdi-v$l5n+AEqb zzB44Cs;{MmmTsfF*kMC%>qI%Imc!8Y_Ut5wqnoHbUu1;r>Z;_=_9-U7SwA!>EA4aT z2DU!5lp`2TbpvgrG&g%Ls=xH-rWar#hDKI#XuadMKtU{zVaw4+sGTArQabeY~vL?(TB+{U_VLorO z=NxU+VyYD9TlM~g!2^m>SI=0GNL>l+PdK>iL`1Ag<}?`5fXStvG>}{Hw`H#zkBHH} zT1S7e$8w&V_zrYou~)sB*2UK*-K}pPadJ1$qA-9>>nC|P;1=UI&N4aO<(@g%x-x4k z=puLDSQrX7EKkStxWVAd+4d2q^--|m+Cs%7V0?GLo&n-xT`EVrRrX9XD5oxksG%xJ z{Mz0s3oV;Ur2;6;je%NhG^(6H<-#@QqVMWP+7qh@+HVIf$XdSWJvo5RXZphrV_IDo z+UeR^3Xly3TWgK?2;cISh#*qpPSB`VJapu=FOq~lfI)<=52ep`O63a~1IP14C2*VX*RP2S9{#=a&rcJ>m7f>%GVEVRUs@aCX*A#j z-M6R2{ZLH++1{sZB7|R&0kZX^50udb4{P-)@?+YX$T4KB;7`NGe1FO@xd@x2QAA?m zvS^U`ksdbBwN6@$8R1l%m~DSmBxsendp_LZG95>4vmbr-EgfdP^*c zgaN>RV)1z`sKlgg;g0& zyuo_<3;LxyvR?Zm0f0Z#?A2cH)m%4Xq0{M27WpMLr{lqKP~_5$C|M^p;b}_!k-^vPw45K&m?n<`-aRt_0nFuZpcX#^R+n z`R2sidd@)wiqi(9f!EP9s!PyYEl0Yl%k~xT0+QHp^>f|$S%6$8jtp&NI5X?X;Pur* zneB`M-0%s5vSAU6Fr;D+Sa&yQz*8-n&MwFG7G>Cc$Y>36VjM$?=1~)It5F*rlmrHN z9&Ip@%~DB@dIZ^g(u%XRNF3;};~##_(PUIKI^pMo9+`rHApm&GuIsDzhO&H0qF$%h1pD{HfzM4o};zDd1EBGM2rXev+wt&UyREUk1<~+)+i22di>fjJ*Ko1M#p!teDd|twLgcD{&`Dd=~zK9aJu#?4*VYa7=v`35a zbM^*%&x>LO7uFfbJ9T(#UrvC(UwafeFFm!i7<)b3&lk>XjaQ z!vKR>v$v&eD(7l<4v9(V^7%n8d-uLCg;zI@Q3sgJ*CKB7<&xu6!*+i$ulT2~ZyI7S zTG~FDU_XwS)nG%GMsHdRU~_sCkv=PRktLwC;R)=nE>+*e`rejnfcDPf$Dq=R-XD8xqQZu5EIlT%PvzDl^Cc1Z(Y%<`GE|&j;GJQ6HcYf zF8%#J+A;kCSB_(#ixZ9oK}PW5Xj~`L{)bXJUW$V6@|5t$?T#jXoNmS!P4W43^IUUq ze;_tI%XLB5Pd@=(f+RQ=?cG*7eB)OC(|6DM0*|xd1Pl4d_lxKW zTULPI{e{vD_49N6(QZnH?Za*v4Q^Y~x?!r7sARjRk0^ukZ5VPNLC?`}MW%{)HW4cN zU2fdSKqj?M$6wawYmYqk#CaSvI-Mok*WdWwLm_LmCMkSz6MI+Qd&`iHX-nx^O(vDo zn$$h<0y1CQh`b>#^t7~_! zy%O7AB7VfB2?Jj9$PYM=*bC6PWMgm3*W29~H%KCB_nADht*KFd{&Pls0lebTz)&j$ zLaH!QoyN~415s)=hr)>+suFtbjMr(fOHr!#w?6olB=7fbCvO{B{pM?JuRU(B*y_53 z@6#%k!^NjP7}Xk-o8JP27|@V_wG+vXPRe4X(R4f$uk&gg9146m0q`TX?1B}(V(d4W zNUU89YEcvBF7D6(NXmipnLh~Ls3r0;mfBbP0C&921Sa7Jw)5Vdrr$hDe~FQt2SU*i z%7|9h$`g=7V5DXn|7Hw4H&w&^hUG7GpDp zphmUz5y^ikISV^UM}AF=L;5Kv?(}?FiAsihR+ieNR^Q6FFHv;i`qX1&U|`Wun51+B zNMVjq5|QJBkdbR=F=(-(zIlAhWw6qsaPzA^6HL^_T->F&N#m?IvvqlRI+ol$dAzz^ z-N9z8`5zyY0Pf2|hPPfNq$Bk_pKlVIxf=DtnL*xgg``I-TY&OiCDtDRV3L`zRF8K) za!n7VI5OrvTWr%AdY^V)eqpg) z`3#$c;tBDX*q}JdZ7}Tm$}9DH03o1VkYsdzGy}d#bTRXfnWChpra|It<0z}^`PnCo z_xdn3eUd@b38aDei6LgNi);q*7Er0Ci#Iy$J}8$8bCVIg0DNZLa{TfsXkEB)My&U1 zgtEfA7uY%X90z z2q=}0LS$nIQ~}HIiq#RkM^4;s5D6F<06Mx~x=(OQH$hQW`5!zU;O93ertg15CP>?J zej}EQ;MQWi7yhM;Cq?j8F&Tb$&2=_$h<4~`(&W~BqFS8_V!({x!mX^!{b>lEG?IT6 ziuU1vQG%03Fx=2;Hb2qsAV?xyMZJhS+?s;ZgbY>HQX=>av-o4qHaE$Wj4|N%96W3k ziJLSlGBV1t0alxWAB3(AOrnEWIJN9SkvX@wLf(RKge#WN?;ot~NPQ(i@fP4-(l|mC+iIkXliz!$B!cx z`{ed1wV)PdDeK)kh!7Swf#QR4{+K@m{==wJq;=r2i9x)PtKarSZ zrded(#RUD&HGDseiHE*=cHrKLMK#wY4SkMFR7v}tp04GMZ)?i~^huF{J0hxv?{W8} zM{=c;{Ez0RWC$fbJN-4!((p0nd6r|Y;DC@J8J?#_{p|yl+DA;fUrd-c*YUQ^*kLZA zSOZH-`TvLS?+fVJQdLt+`eiYcxi;5v^xo9l0{ZFZs6yqV?aY-ax&LS9TGGf}+Wv}= zYmG9&5xz(=n>;e2{NqIf$Wh$i-6iMZZjXzzQFHq1+3q*Dx7$~tw`y^R9_WEC&vod9 z3CKw!dnQb&(})lWJh8+X>FI_Hpl%6fM@NR*A;GHb8B+;PrhoCtHjr2hTMz{gG3ey* z(7&D@2H^dhT!XU}ZDtITZ*g(iw%K{_-zdQ((CAmI5`X zSYowAX3Pk7_Tf|Wz5`YXImNx+0*je1qyF@NrOdS=FfX8M!6 zr;(AerRBOv1{x(#a{oWZ+QTN5$zrulXy)=$3;=)khD1ILsA9R=j`f@}cK!5XIzu*= zS&vUdbd!q+Z~k4^z_@j1Vl6;t9;xU+VF)WpRjx5xs?vzBW1&CFg-b_I@N`?uh^#8LBaVy z>i_vG2?^=Na&riA$m!bC&fT~#=^p@lPbI@KyBZsu1ABt8;||As$@?#oD3z|0r3 z`sd+#tS+5Kv#+yk?Cc~}a(+I3&mn!kN0KV=*xk!4K=TY5OL!+Np98wzEwpQGpDwnx zVj{4Z&6f)L?rAs!Bhbr6$I6vPK04*{dVz&tG!u3YoG+u-mE~1YNosxIe>msBPcn9M zbq#-WY)VTzxdXDYS}yX-DazK8LVUVoMh_nvDn2sBu!3I7*;$~TM5EJUW~|H2?TOZd zXtZ1>NE%6FK`g^leP2)lg8dQKXE#1a;z?+W#N%|h9fak{+UO8iLBv@(z6<1SJVQkA zm~8O4>8NUGz-4z0&eDmPt*gsedFbu!WwyXxtP&H54JctQS?_Q- z7~I-{fWEi0uF`3;W*MpZ8uB<{;WnPhjVIe_dEv>8jg3uBO)MrhRH{-TB`QjNo`vQ* z)=BaPo-c5>wuR<(d|m(nKrYdAJ#TLF+Fg)l0ss~&#&_4;JS)y6IvC-YmCu{D{-mDQ zJ@%oZ?QPZ%1$EZ{@KRK*8*H7qz?yefKKOy>fcmz9+r&)-dqj+#x^(F@7dAJ4Wq5c%W7d`dZ;ZXPiRlo=dR zsyCn67V^u?#LZ}_+o%P;NAnTIWsx`?Ez1#qWoKJ_Z?;%?tX5d%UOk>GRVa>?k*)3T zq}F0K?7sE&^aJIW^~E1L*im(&dw924Ww5%J zu~=>>(QKC2%>^LIJeVpNjb*&fsL=S8C|=dq)VN~f+^B>2bF62Rb7b^0Be8kz4|Be? zgYK*9>Nc;PS(K-Folc(|cb(RGG7~7DPFLK1(8)0CtDO*?Mu0(vd6Y_Hd9zzN zC-4&%78E?&@ItpsmcO#X%xxSR2A%OJbmnA1fB_opKT-hs9dBG$!J>bHoq1Mzd z#wK_xm!4SSz5^R;)bD>I&HLE&G`~XjUGXjs8iVMO<8E?# zdb*qcD=zNP&XpNx0W$IO_S)XTZfJN^T3mc8Ej;8Zq_U5ww$aYXNud;ux-9R)6H)M& zqP=n=7X>>B0l`pbWM^e19bmRXp|!z_#&7Bu&=|;sX>MMo!#O-P)iphRecB8M?MKh? z{6#EQT3Q+qotZ+CWQ--*Usnf@rIE(k%9u80ih*%=ykLs5DqlP*B`x_B--zYWpkhS{ z0v)dD2rHNG*>2eyjYqAuS7gC|ab_wozu@DjGcif{o|>M&%?^o_I-MUvv~hVl?xWMF zHMzuewq9O>ft|{HIj_A^BmUH;RiU**AaFwR;;{Yarh%e8Kw_Z7z>V2)A0=;{?QURw zgu->XUOUS3Rb#$^(Ku`4ek2iOn<=5Y!RN=RTu>8VR+*0>vM$tBzdjh^cXmOm<3%jz zqelMM>DPZ*S?RPYk2wHYIoR1d1~Fh_f1yJIe&^ybTVk6qpzrK_+t$y5ZnR9HnNTjB z0|$E-f}U4KWKfvQeI#Je7=O_Ij~2jNFZqD8 zW9w+MO<%9{Le#VgyF#vV;id`NkARUI0x&%@U0bt@t9_2c zS>NjTS|EJmt{n86Jn*TJ;wy|VzIN%OF?t~3`nCDjM5sW$YsDvuAqu&esiPySW;DbE zZxv4#4C#dl?%d<%N7oYIr${IF>zi}9sntN7bWz5QQU^n!M#T>8p^Xm88fg(=`#*@65;EYna#JkvKYQqw=XhqNi{_1v1aV{pl;M_-U1nr zp3Jifk>{CWz)0xYOZ)C86*^21Ao0zLUkUgSNrd*`z`<*A-Nsvye?>zsX~2_~aM~&a ze0I}NOlvdKuL6_E`MV0=V)y0u>R0FYc6U0KtJM~+!FgCeNSloAEMj`jt*&Z*#61M? z<365919VbamWn`~`ZDu?p^vRkv)yTNGHC(_KFvB*1U)eQVOmLAMJg~TD%s&C`b z?JH5tlIJ)*lzZyW(?Mnlr}&U)_44{?f#6+jYmrUN%rSb-sp0Cn8O2(IKpU`@xBMx* zoQmUVpd{Elc6`n0aO#q${Iu~EwFM~P+aAHqdw$9|vIX`A=Bb_H_2x*-JwYAnikx8- zgmmyi?H_OvjncYex(}arnKW+IpT036Idk{;#F2>gH;U=BwY71Hd=>^p<44{OL4offCM8ajEPxjp?|2Pbae8b~xv)gaHHgGvPbeJUXt5A^_jzSt zU@*|vPZ(Z(eDsfAc@PfAE>NVL&}t(88lvr=l$Av{AzwueWJ#OIXvIgqZgn_oGrsWo z*he@X%@64^t+#outgRscKKO1mwQKr~$nP*uC-auF#n(hczkBtsJ~lYl&M%w<58FFC z_3CGJ6tyk(7mMX0XrX!AS$$TTokF=&mdj1;Zk4ol`(yFOh8N2nBBgW1b7n5ej(YG* zwgKY#3@MT!Az}fw@=Hsh?n)i?^$CTI2aUNMAAVD+TrR$0<+=vjoR(Ed8<4e~be6yr zXc%r9BdmskRQ zEQB->IvW5$&nz>8i(z{(&5Rk)%c6titHJ&fl}k4X4v?r)ul3o#p}xgkCjv2}nSQoy zLL?j}{)((WX^d%&SRfkBB(%66IF?y;HmX1a<)loDt1>ySEXEmUeZzg-KrYj=805C=U-gZv=3e8<5adzpx zp;|Jrlj909H52^q8=2B;Jo(m64lI@Z-u3hZbV5Q?m7D2-1;-Y2)MT=uLs2aVj zR3SGO6SQ+S@ke(5CEDO*oO|v=IYymkYt^&gN&g=Zd4{J4^0mqJPbpl zMH!#V{(A3P4S8XDoC~7(dh}}X9pF7qda~_f;utLO7$ZvqrR!j;hZY1i(xBsjY!X{( zb;^9fpjz*mD_2YKIzHYveSs#05npA(0t*QLS;^C86T5SpHLm@d@`HGFmC*x~VK0g( zb;@kM);}K-g||AE%^n&}^of=lv~{0hBSX}aX;iRlC$Wk1pAPI$pqe@@F)`-8p>Q=j zR&dZfU7QnI@%ho+)8f6w$}%b#hU!W8Zwy!TFHtJ(+PMW)e-FOkuU~zr&oD-|uf<3ZHg3Lpo5KG+DJxVkx6PqWQl1T1(4lqw9~x5nrKXl5 zoC|jFNgR5lMmU$Ah`YQFqWJN$`ux9gQo3|Sol)uPYx?=$zv?oiTr0V7w0Hcy~VsiVPl)%T4k0qYHf;eC2gnxkd?Z3jM_c7HqTCIlfe-^84|G0^60Bbh0ac?QG)U zF4Y;ZLZ`hB(a!yNvpym zBlv;no_0nv``Ul$1Mr={x;fF28B=ZF1Rmz4s1#rI8mO?|QzD~E-2wnVt^FCC@N|qN z?0nXX<}v0tQc*(9W=(mYgQNx?$}wkqr7vANIpK#IAFLm&<1g1eDSW^?7JEikW`xAQ zWX(+-5J+L?#05n)21&|>%Kd|^?TE$(vR{bU=b7E4G_DO6NhJeaviwT5i9 zDCbNUi|q=9Q7a<(2j@|hoZ_W7yKNexi1=_+uWR`PfODOgXkTaAG$)qeO;_oEB>U^<5pk+ow^f|HpGV#bMlD35othVZJlN` zi7Kp$Q+cq_Hcrh>V%w99koTolA{5XU1s}OR zoNVVHfNVeo8WT*Fo?%QVzY5SIK{_dY$iFz4)2`RDW9A9?h~{lGKg1i;ZsFl4J9V#K z5^rtM-QW~er872vqQ?=aMj!*r9$U#GEnhi_gHG#p-Lm@E6U?cTFRXBI5$jmCM{7_{ z1Zq?hcU$Ky79P?ZrP|ZQ_+#8}E-YH5c?ZdL!T#jG1!Kgeu4!?&<1OjE4ZhW{=3T=v zwwAl-g5E&AF!RaZ?j8c;%GITwN=||+ox-`}`ypcYmlrPRt$)@&cZ?GbN_5(I-t50V z#2WzsiFJ+$r)(`UB3?2;^(17w*WGa-iUUT>8E51Qj%K?Nej1M2qKvPg ze(_TGxk8!7eP%8T=a$}}0Y?j7_mkpOsML#DqeY|{?sKG6mt~j3MLC=ot*7Dxx6#M7 zA_!%Zg{raWw+sl6Uz&5ZRck8ea1?;D(0>xeCEt4bB68U8_gxld!HnCHf;r zYIUt`IRj-bx7JvVq$*J=q1eh(^V~XhjSjV2({PRGqB5In>hxVI-b*vSwb|RBR~0Dv zW7dbLq3anb3ZwHdN*shBWfTh+&jBQMt_4-V?(ubb4rIDwvue$Fl148crcLFg{|< zM-t^`d(kE+p?R`Mdj%r~MYoSnX}m zK1HMlW~o=bwJ$+HiC zaj@XiKm*YvYj2j-dv7hzp|atH=@e?#T*GvY--d4y_E( z?sk@4BQ~!i32D~;@vj7xQ;xY}rCRUKZrTA6Cp_kUdeCLm%mE&}5`?TWg9?>+OfdxV z2a+Q{-`Z%QTmCjyz2iDewB^v1>>ek-V>3%SyvLsB zBJ7*ox#!(qwoZN`@muGK)zRg$Tef`IpBHDprrn+!fO47s7EiE7bu>4l1R%Yjc!=!wL@yDJl=Sk4rtBwmVL zHT4L1G-MY>#`Q>K82u9y;E*Q4laBXkVs2t*XJhyPAR{Rw zKDjvO-IVNRD+@#?$Vd%HzP%a{*fx!uEN?(M$g;k55lgzybk1X_0tnm9XV@9{Fi^j= zGRIo?DWx_dX6rXpaA6kEapARJ@82sAFqnIsK+is){>ZL%w|5=eH$a-HqXzse;kdv0 z7Lk`&NdkIl%-qUbM{m7cxS1+;F^lklMlHkXwA9H8Ua9wbH-P#O@2FwjZBEZDoFjas zCq8&*kBjsO0&*W!GXv8xC|wf$>e;@ZwSYh1xkJHWp<;uDKEKK_J-MCz6^`5TTelhJ zj3O7u<%&q8ylY7f0b+Jc9>S26Nz^cM-wIZl_^MUdObDbh5p@j=X^`WhRTN+MmUuZp zpe;l@4iAvs-FWeYKCRBG*X?KqWd4ywNcoR}u8j>`$V7yG>ZPZ#8;);ZA?Gc}|FE?d zEfxl(#p=@)VLaxIR{T-g7m_!M)BGai7YqRSsN~*fXh6+f+cYL!)2w2{_<3*1!Y^v- zbY4xDz_$07o3C>}4f$aO$zZp*^C~^a;2N-Cy-{%Li3ZLks{+6tHcA2T0%`E?ZDGVT z6BEM|^FEEWtW-6X`;?;3Cl&pd`o^i1fwdE}ucgkaixS~c4&a-?ryttC3-n!`o&hO+ z0vpGA-|t`#SsPN_@feP@Nsb>%>P;btzY!`y4^MqtMwTmNR{8pc zN?0~}oB+(^$MQ&erz0ZB!A{jYFb2{a+<`WlH1lG@YwX+F4D*G&lfip|#@v)w{^*jyVIO>ZpoGp!DHX#|?go5@}JR4Fb1 zKmeCq=XD#MI(>M?E2HDFD_4I7nvW`z`L(Xid(oQFcpZ_<(jFkXH{F&XfwJ_w-ecPG zwZY!ezB7zEB};eJQV=2{yd2(b<2K{GX+394GtGHrDrOQS} zr`ptadck`DJ>Y$D(U;GI&v=mK;w^5!w$_N?1eHLZo#vjMZy6<)Zm}hv1|sd{T$J*B zvklA#r0XqTt=v77w-}8v*b=qJM4XC^#R@XltxrROiI3rz*+>Srhc5&~Dmx4F!Tu~% z#O!A=K?DB8W7&D0L>)Aa*O&9!4uxWztu!n;Zxvtf+@L4Ms&R^+1z7W*Q>Xyr(ueA7 zi-oSc-=28HZbp61S)NiA%ct`i3=k`E4`SOqR_hYbGELO7&q|k|kUk|j-N`bta4K!; zk|B3AdBtUmktP4)}COysFMt_C@;McJ`3E%-oN#jxseR{wr1hE7O-EhIalY9@O zs7PG0f5y~XDw3F})XWR;vyP>6u$s(E3EDeI$Ly;uovs@xVmD}=-Q-@XbKkq;(iO{0 z@}o(Lb2_+&pg{mY0)cCN1NT`1sy6w@o)wA20B?3y!c*;nalx6}AkSYe;J~P1OH@dJ zx3{g;YKxWy=hKqk!*%#_9ULoRFLU4>Qi&iKAeu=;vAJg-Ec1AGKo4ohG|s}oOF~Op z-CUAi&mz3s!H`bnsAejYH?Nf(#Y;^0Nxknmv5!*V}<@RSID^S0Z%Zj78Xfz~j8xoZ+JN zdTsz`TwTF}fgs@f$y{BdNB5Piz#1lw>fX>-HSLu=iPmb%C%QHYxIl-e3bR~Xj3pNr z%x_5pja7EGF<+4)bp}1!nKtm1`m~x+QkG9oQc9IuvfQ%w+2^eml}Q=iZvZGKr^n_RJ{m_`~HzsJCK}I2*Grtv zlvJwTZHt;58#CR%@$}sA%9dqlT(&WXaUG3*a`Q~Ln_pJ_DnW-_r;cJ{z8>6p?}W{I zm1c=hGFwhbpJK;=7nG&hWN>n&QOxmt$Jr`M%4>7cNR&cC)&GFbxp%T^{19pJQX!D% zHT!1ukscX%s(c@-iIc{hG;kNe5W(_#KTRF-I)#<`4d$(=-O|>#6TC7jjpItCKW@R3 zlEX5E{aR}=(;0EhXuq7D*`6xKRuZ3lSzCJW=D6K8kP#Lkvr@q`p)*6OU?e`9pw(%) z=5b!F`Thd0YS-OSZIEAz3Xl7)K_!gq2D}KQ-}%nhn8*sQD^_c{GK%3iQ*d|4+?*9G zPolNb>Av+$G)2j%*0gyII@F)ANXOf1JZ+tN8#h-z%}20HvU=x~7J%^Xz<*w>gPr&- zEQLFrr%~r*mZl-sJCbR{gCX$|daDK&JBLZqq{Sb?6mbz=*G4M?6m*{BK`AcF3WK|2y&jij2ejPjrJ!Y5YH4?_(3l_&<{H zLyLp)KkWa;!TdxQgNfflp&rL9#peVBb=SsQ1W}Bm_E+~o|N4kb$w*#3xy{=t7M$*G1_CKv zS=-oEZ39u3Fri_5bzuSbSvzVg*`6~4^$qgm2>cQQlo5WM%yknScnRX)4YUd5TNbn% zbd@b~QVeu*)s)_zKypSqR6dOH**2}7j!!WusoQY5n#e8Iq&G1!@h>nD^_k>1(;I@)?Nb8RXu?uM{djUkxsV4j zDpFf#YQGZ1y9;QT9Lb)uRHb{G$bk zI{JEgdZr(5ibU}M!sC1awZHN9_Ex7H;xw8=xZ}A=jq%o)tX7t=23YP+Hf|;a&@45%7&@~m{fVYnaXZd# zBPKZ;rOUGp$@GY|x%bOn4R->2$R>0;+kol{NIB9ZDy_`v+7o<&7&q67Og&qi&@!4{ z_tW`Q2aWHugMFRqSz;RvR?NmD8wV;6<%;ba!yYPDt8FgsN_a>ms_T6u{kmmM8ti8B$VE6hMnrZ}(?Maa(&PyW~$dMndvV@z| zKKAS-NA0EpNVigFF*l0|lWIT6&QrUP4Y8XCrey?gwc+7dWS6_2=o#7$Y~j{k>a;m| z`*>#bTzj&=*TkZna*dm*Od;)RW}4Cot^rL`jdM@-c|l>pLcRWRMY_J1OKr|Z;Fy^1 zd|q!;Wx+yu)ZWWk|6=`Q!4qS>WA7nGQdYrq^d%DqOJ@D+??+#86PQ47RyA#Yep)1x zOnT)^gNq%4N8fe8zlp1;hjJ%wNDV7b73Q9geS6$wFXwmf2U2NZ87cEk_s}XUPPGt@ z!dkCuh#2Lg1Wfu6p1ERTm5v%Q{>G(HujBT-d{H3Y zfB@a)t>+MXfTy-IYh{$rgm!y|W<)jCC*3_plC;1lkKHlxYdzP1zr@VHu#$ z4Dl{kG*Q#LBGLB*z2HLwoIGIdhk0pv{V{xZ?=u2SXpX}QlOnZ~p5BuJ$urX3{gj0a`UMM^FLFk8Df2d)enxYm-Q6?l zU1fLR$n%D=38DZ0!cL&NcSONv`U zx!Li2uGBaV^NYs(d0QoUOk@3N_KaoLV#Ar&%0mXc1PkMPGZt-A_`fJH9MBDw1e-FZ z0bO}0(Q|ME1TA>jxd{rY4t@G>Wt;S>u8nJMYSQmx{g<5hNJhCF?9hFA7G*Y@B^Dcv z_?JSP3>qFC9UM>e`oDO4tEjk^Xl=A1KyZS)1rHY7AwX~o*0{Sv<4&*ycXxMpC%C&e z7Tn$S_St)%xBumiJMQ04{jkqgFsAG5n-Te(4WE5m+OA!Z;^vKu@m-fA?IWiPdR_O-0JOWI`cMG3JJj_+ zrrqaK*Aqi@?>k^_^$4{xmnQjJ0XNy2J0+J8uQmns z#;f~XN9u;ki9A8FmjAMpG0%N#WZ^4VL!_EOBr*)3E!K%xT@ZM@bnam4qM4rn;75(( ze$Pa_>M7bAi&5y&gl)1c40>tLo9WoT`|CfX?88)9JC)=62~k8M2c#Dh zc-MRou)ldVNvjUlnn?;;UGHUVihYV*ZZ^T`1r0ZLfAVm_%_8^cdfGtGrI`Gy)wX<9 zmYZ;k+h4>O-t{72wLsTrulwBB#V?S0Qts(7GJ+OQdr5e0{P(wZK8Eoh&F(w(cbHW` z`1y`~)f-0hmS8p`TgIG;<@va9A#YLqxmGdf{ViqXr?U{p>vMkRmrc`35tGByfHYs; zttDzl*=qqY6$v|BR-5y1Rjt?kip;5Xr^CCnS6lgtI!l=o8vKKQCiax?$TP{7!VDD^6*g)m)aHhPO2Ak| z+_P&6Yt{iDNFSv}<3urU^QJoI1C5Uw#!Lye8Y19ZT;rb(H6JEk-k0mjjG`>cG-O0J zoJcu)0#34TG(W~{?w8kzX`Hbn#Lb9w`FNe~=EZxx5IfVbYV9*NemmGucG`H}ZVG8l zmAwy1{gqwpjPQdEY-2xfOVJr%b~k6Mk1Og;z}E8daq@XQ$-Gveydz!s+0t75K6J>( zv}?a{?d}^0pGv87O794YC#nX?TI?b|AKCIBzrTz8n+Xeod>c9TCUd14Wi87XW<@(Z ze3-Uu1B#jqXz47DkG>e1JJ0oU`x}fPDwTV^TQdPZ0V^}2lDO%vWPc{I>{0R4k*p1g zHh7Qr|2wLa61rVBVxWv)G}-SZikMLk@f%k-1YVX}>vQkDwn#~X^AAA@ zv{M?^aKz)F*x#yR1$$`x9K;qT^Qc-J(^;_kuySICJG**JV%RC~EAnnvmh-sC1S#*M zJ&uQaNxkmvA(i9eDR#tdi50I%r&4>Vr%92AJG7?gIAuAxWQRwGq~|7D8cY>WXlEL1 zHjj&DBRnu0d577GS@TH}<$URTLT=VYD?PZ(jX3K%_pie*7^$0WoR8M_LRllXR<77S z7rO58F=_(Up7#SLZBc6rDdw@e7_80bW}#Lx6O^gC-p?>Wl?`3Q(`QGew+`=DOUzbz z zy2}U9&5o&U7b-*DkAkNCDxSQT&Bb&o5Rxsp#M#_Njih2&p~anv|8vFIlR>I70@#w+ z!-Z2KvnxgM?wR5%?i@gFLAL&Ok!kR|X+x$3ZZhZeWjv{p|AUR#QQZaU zoo|!tMrFTI;kl>!C5V?5DTzKm1d$64fHz~vncAYw@(XTeY^%0eJWia-`{^LO_Rq|; z4(l25gU&US=(H(;pMiNclpiY;6|+1lnozyj##u%h?wAHi4)DFTCc*vnM7Gqije1h2 zhI!uhE&uDhUb%*vlx&57tGqLTU|}K(xI|c;Z=pXU#BPG)zEg4j=-2Nn+tcN{*C+kW!4a0DOTIR0;%zI$LeJ%K6@}R%R*U(AbKc-2 z4`oWDrk>~GnjFlYw zH=!=RK8DDH_Vfa7E7_)7dPz$+1Wze#(IxCJj{z9`IT;b~>l^JsL=lc1Vrfl`__JMd z*~!;n3c{I@J%!cw+fn&-EJg_8W;6O!8X7SkLQE+4F3;R)n zW`u6h^f9|QQknd$?%In9UE)1(MCeW+hA)Dow`=LfC=sTUCxjv~@{XN1{K1cDk$u@nY&@}59gzt@KWOB59V~Z*J zs>~XfAkT4BGdio=Wul2sJ=hJpLbYA{?xRA;I}MR2NZ1ssB>bUaF-sD;yB_nAw{j&y zXreWYvdb!ZSDx&Dr}nQoK)hzdG;|)W5n43IQWYxR8|Dup))}}sb8#ldD;Ae^%=(5vk;uh3UoM8Ir9045wa=teEFhc>ThQ?YUIa=WsjFp+B23^%TXXre62;S?Qr}&?>Q@d9MSA)4s3c8E(0Dz z()C!zXbwVNw(^Y2hn=eXA!kSm|5c!)+HH99W;7~RQow01a{Uf=xZv|I-_4^A5Yu@I@&n*F0w^#VVHG2?y?hRpKWsA{0Qfa%e(~lFXu`HdosSI+UV5LGr;G zTJ{LkB0X7sdP2-#8S%{$DL7WTEQ5G7o4Q2=Rg5sc0K|yG(AX_*+G`e9FO^qb%zkNY z)rfNCzyK=S`%$W7jtuD+)7}F3I$bI?%Q0jL+8AjVqU;DuclUxAxMHl}Nq(Nkrz!b) z(yWo(Zz}3EPJUV|>mLurGb8f8kCEfY=@$GrM1h%H{%JQI2aT?#r&wH#cxu9_jX36^ z8kGC>wdd|Ndny@0AL-PX(+jmf*-5%Z+sf3<&yxOLP!GD2%R7Des4ph5;goPLp z1E-I4LQIv-plm)4#E~)F?_J>`m@H`Vs$ z{Y+W>_F-j>=YA)7!YZ@*Ia0gEK;!wT=RC`TuDbRX*36ZW9|-^hu9?jhB@d3!Lgn+i z_;##jG*>Mymevb=iOn+xY#uE`kcc`M`FWoXKX*h5HGTR9Y;%(b@)pCC?>%O-N+rKP?*O`E zxp``xO-wA=0De=m%<*dWdrMIgq>1a7mcF?jC|oQty2CD|q?~P8X2RNq;4(Bi_{zR@ zUCL^uJO6zyT>q^QYf4VYK;k(g28~cg^Q^?Cz8Dr@f1`9vaJUsK(ERzG8mIb?;SYLj z>DOyc^;j#4xKKCiSQe`tDrw?B7)z3rp9RBU0AzA2TcCs#BO_*Xd)jO{O_LI(UpWYn z;HNtGX&i(Uyhif9pQWJzesH1&J#qQ1w{Xa1gX%X|C�O+MPvQ>e*inpM5~)`z*>} zps7HMmzWV1;|R1&28$k$ZJ%V_hqpnSW~ALmOe337R>u2WNF}T^~Iw=Z5&TDxOrw#qt^7GAw*7n=y&ZLg>bth8#+1ZTQ z*0UEi)Nvel=WC3ku=u@3%qkzcwb)*pA?CK~+Y23www7(et1l{=Ia8cmTT}6|+1nI{ zcDU!>O0+ZV_>T6?9>aIdLG)Z3M1)st_^ie+xa1f*?mS_t;1GAfH$(wX`IN;8Il81U z*hYuJB;p~9mtD1hj0$WosG^sJ7Ey?_s*E(F@vu_(UQ3Y{RgLV{*#V~xImp5U=M%Kw z{DSLL_%1nxw^9?s%4759q74F$8ZDM4{(vQCsw|M=_@#*rBZy&RxlH1d8&3@c0 znEhP+cf1UrJ;4j1@3r35lL5Q&)$H(9xt&*+>K>?#?&F#*HkN3Hn9^5QS#N#37?vw5 zEFghm2^`N|NTd7?QRm{$`-t*+IG%vRi6>f|wxn}K2U{@xOL`FNl5LuucED^V5oVL8 z=eDuOC}XpjqfrBgv|o|v3ml`&vQFpf_kvCs@~Zor&(a@0xFQkpMWtT5%X!yiZq4%i zl|Ul~a`5xZ}>J@cuNMqZIVZ0}^(O|nw*4-$}^ED@(QZm>3h+c^)8R_rDRtmrOoGO8`=HXf^6 zJaZkbf_)k+SX_OVVz&poG8_hJuaLD^9}|$uKvMi`Rf86Q*v(4mp-cA+GQ%%t>i&jBdxHuQ z^sTWJmDDXQ?pOE+tTx|~QihASLCY32z*Aoa1bo&~M@A?K%UvU`$W>yjZAFc3kM(_xO07zhmenM!LDO>o`UwGX{*LO3QoCGcWy0W zg8oNyGqeiy(t;(QN6*C%w8U?gWvvY^*HNe+A}cMfvL&MM(aZ;P=qUyqIh&Tt)p?(h zm?ME@YW&>Ipx1E`oM>R9=>4+Q$ET$$n{+qWq{7J!-~pTd;{z&&_@qk-?ul`GyV*ml zgdhbC3fi8AGi8)ANuB%Y(dglq7aUedYHIwlw!$x0nz^?0crs8Xg8-;b#9b9?eR@*_ z0QA*X7T~huChS(w?~gfmGAcHp0cd2@UXHR4=dPOz`4Ms}1@$6~0Ckm?eV;K{KsMRm z?jsn$C(((-swU`9*)kmuueD*!dei~s{lnjC{8$sjNOV2|06fD&Rawv1*SBqn8P93^ zVQ+fCMw=g#)AUyN`9#{M>WV_=?MZv83TnN=)lR#ywXAY63j4o1b+rm3TGE9zG;i7j zZLFh0JnaaBIT5SFm};F!SAID9&c`8=Kmh{tyd|4}xnRQ1$3qRVQNMybYUrY#pe`Hn z(F0ftE(uL(-l);csr{WEr%1`%<_3q^U7{cnvxFU6-*hX^w@%5}Nwe}Q1wKz+!m4vE z5YL9}0|3yA?!leX(rt+3h9w1PcZ(jiTYm@Nj+=|8xnK4Daa0(22yIVOEdR9O`@mZ3 z-^!@5*i^YTJ0JELbbIbGWIfU4dDBKI@LtEW--)O7+SAdQ)s}yvRdZgLipvWE<1#WA zNDO zYx3m!*|M2&)TXb14jfoeAf8L(kv}PdU2v$*d0!72$_b)A~GOWh>xEU1u@Fcj(aIhC=D=_(lbLV`Ir+m@lHJ=3NWK`&vJ)tqx{ z$>}+45BPf2!&QZosM`K8G|fs#7>WVr@tn^lrW0ZQ^c$MjtJC0n9JsA0E7B!(IgCt0 zn_R}M8sJy>hHKT68t+BexaD8^bi*NM^}$an!TWt|PfJV;t}6(<=Tbu2d2@3nl@;+= z+oWI%DsthNdrst+hsRs*Y&{#wWHifdScnZx+P4~RecXOh4#ldvE9xTP>)ft5ZDc%h zq$pw#RGacyUY=xS=KsM#++ZAdJpRW3403laWL#@>09tQ0=dq*q3&ZCq^b5_=8sL=7 zqB4_A6u<45RB#BXV5 zNhLI_FBnPD)OVFzf6I=L3&*r^=wvR)l+{;qFPe95piy7}tCxh%P;^y*~{TTl}qc0YdML4?M_9X>h$9z7iUtwkaw)H0kZops8?w9?TT-q-t$*e zsfYC;IL|ZB?AICgt1z=9TzP3=vq*<(Vhpwx9kHrb;jRdImnNeIg02caL!z-!&)X-oo#JcRAl~w8W-Pw$>*! zR4Tw@w>m#z2vf{TkYmr!+Ju~MMlP6ZE6a6iYInq}Ds(lKKxFH-lE3qXT5=U98iemh zEbp#y-J2)0YXgk&GV`hNDNH2?%7-Ju!f}+6E_&IFAQ-5HVD!^Wylh57FEHU4L61c)MFulZ+LzHx(Pf|n`Im6T z>m66}iJ7<8g>CC&25~r^v}WDfgQ}j3)CwEuax%me8nDFc8D&Da=t9NBsNuzbdn+X5 zQUQ01V^-0HUu7ofwn&xAij8ZPL3O7T9MiS(A(6$(amz?oyW6kZkGD{Y&|5s_+wemx zgJ&&2=E`llP(^2&D2LstXy~mLI1qj{Es%TOI7$hliee!&} zxl7BGS`H3&7JwJ)i|`Dg%V#=&{McJO|5L5%t(S1F^lY~$tcJXJkRB-T0CG5=j?3#v z2XUT-MpkKf?P}i#dDGaOMq{^}rWMpsiDQ_Ll+!l=35C4+@-;In4O?f#Q{Fd$(n&FC zP0kz$hY~`ktw2iM!(pmg5|RiR*R4IFA+smWtEyiAK84$|NQh_W?6tye#b=AS+VTqp zJ6n}_g7^1|slng*a=RtNQD@?Z#T(4^nv#&=V=xJwDD)sipjRDbwv> zuYXRzj1XH)O8?U6|9q{6|IMb5l8A&5kFAU#NiO+D-XxVl=O!~~n+Ye)Z|hRSRC5Ib zZ*N!9(7zx1f;IqX_o|~advUPJ_19Ik^4P43$T@F~>psi($RbQ!)#LnGPUvG**28!) zp@14c005BDoU1X~H;41fk<-Q~XCK}J@i6!U{0B`O8Gv!Sq{Xx>nqt-?KbI6+6VhlD zn5HXRh^z{cDTN*AF#P(_tM$ZD{I@`KB6gs-`A>ApwR5n`4oQ_&nxvVAFrnjl>Yz+f zQULvUJ9=PwH^8sdRIz`12&oLHJ2dx|0ri)m`3Iw%p9YFVvHs~-ZptBEPx)1IrbxqE z4)Dqx^zQDx%@-M*ufXqTS+9LQdnQTyCI+$BRrz5w^2vJ`ZlKv{X>lsGM7qp1;?@Jd zZxYHxO#RWD2V($HEemMCH=EE`i$BG)J=ETjReFLK>o<`05@Qu%e}%GTI$6@rwjuDi zy1AF0FUKWC$o^>n@B@%>i=dk<9LfO!UmZv7s5T<&NZCySmT2Br&PFwW{xJTAm(2_~ zQh@%yAZ`YV3!A=tsS?(up;Vt53q_&%M>;F!y;^sEdQq)(OQy%W9k+X}x*{Ssb&*S+ z_|QEbu>B1|!UF9V>s2q)yjmoy+!^Wjm`hyb=V!_JM9!SW%y>#{LJR+$Fl#qcQF6dn zMDXw#dx1;Z@NVH69*)=76zSD?d?MKy>?MfnM+SQ2a6hkDoO1x2x^2a-u)4CPq|87E~45f`g zZ=j~4f0(ko(?jOf25IW{qK*C6`2A2KtPr%=9WrRZ)PfX~E++Y#^_;qkN(*~)-DyPL zUkA&m&Bf61g^^TdJq#x+@B}@b2L=G|NpbGhEC6^DOFb^+kVXCz@H1EHJR(EuaSnr* zAXouuv=hWj?sSP51@0}9n16SeEpIjU0lf|%bD;x`PFi{w-7>oH2^lIS9Uw$389g!( zGZRtl)ZRiX&R|zc)1&a?3r`&R?yn0z9#XH+T3n^D$g>Xq*HL_=fbCdT*}$=iV9!4d zR4mh78`QKf-@k*`^}>S=l-LH>bX^Y%2MnY4)ADcqtsb9wFcAUvqmxR_FfLiEk6VvJ z&9GhS67mF$?&%*I=LN927S}&@e4V>sU^yXvKqS&(OXb5oStjx zbvIsg06&hGUs+G#_^eh^l7h{FD?&)`Oq{Ho%iliuWlyDm^JCFX1VZRJA@b6#u%u1} zizW8~uIsd<5PuVZWp%7(nQ&fizE4DI+Yd`jz_)_Yx{uFW$jS$G`$j0@;+C`Zi!+sH zV%S*Zlb$!UatZ*#aKq3as+u=mMFS01P*aq2JK)Q5|0D0Gpj#4SBN|%4PK${WiXtu9 z(`v^IhF`_=-PAsdPjLxY_HQ{{kc#nCS0u(&|NA@epewT9kcUsr)6rc@>QZk7P1H|} zaZ*8UDDnO7mMLFDEhY^;PBE9}rz!q?&GcnL{P)ks!^)Mf52NGKnB%OfLo&OtHEQxg z69ovta#}YgEhQy2?gh&!{q^4&Oz~?;SDQ1E6H>GaCqg|mEgNE1R{R^us5!~?a^{DS zKy0<_nyF-dpBaKfUv9jxdvSHW3Vp;4_iq2xWrG=*j^3%Y-OcB$>oE#qSWTiivc$E;l8ky-?4TPTm zSn(w$d{|RMJvlQ~%d~*8)c+>X3ni;aYIJO9j0rC0_h89bNHcy)Tqd!ew)9T^I&-y3 zTr^cp7f}m_5vzIVOzjuEl7NWcbJ)}aML46knqrC`BXyrqK70!hR!&EMr>&Fy7A-Dk z;j&V<)9znCW|&x~ZBC#<0q9f#+B=F%7nom#*sEh}9B%UTj`LMx*nIXYkL+&bf* zpU{%$#WociG{a5}!zdC3foWRTj}Ea?;S zqB@c5?%rB_UwpV>p7_pen{V%xmZ!oh?bm5`3yw5Kn2I2X>)Irk*C$!b$RY$8Trp~ ze*$udyjvr*`D^2n;*I8$sQ9_tx7+Fvf~eCC@F9g^sk=MW%1T>2qfdZ=e|@x6#Q39F zxVir4V-R4wIP?$Rf27S)!>|9ETagEr;)VaK0Ef@{0%!D(^85egqwZ$m2DF(=>&x@w z^Yh~x)W&SS&E#sf=WIu#!h-EwYKM@bB+254%x#lQ=)ZIr5x~V6o+w~rc#WBxJKw=x z(*BzOE=!o#VFTmi<`2iYKH>G%rjx$H+a-sJwep*12976sYfERndXVX8YOQY@*+1=p z?;tIAEjN<%AODNw=EVufQY*8-J>MU-s8-qQ_#fUDxr#|U{Xly#+3)@L96!Uj@+6^-T7)#Mj{al>3a}@b)yd%`=#?*W4Q6xc z33;WKqC6)l|6wbaWrHq2LN0T5fmrrR;ulUur)HlIGmZbce|;@hX8-=cWcV=;;KRTD z_%!L3CHf+jy1kGMj`q9izzn`S@49F#u-L$X^1@L!m$_Ll(7V&V%JbZy($Ud4OEmu$ z&t)9*{Xu=>ocjP%F(DbwsCc|}&R<});K;7i6{qKWQAm9KvDseAn&Ll3&#MJaSifNn zqz9teKP@y>8u+=J3<=^A9j$1n_9F%?k753xgzf$;MHi+*fG`@C-!l`+h{_=9Bef7G zx`CX=?cLP+H-;*Ue#7Tq4r1UZk2r|`&Y{!!(VQ4&YWN(OszrOD%!V3SSSy^j0>KXe z1zzit7!0;qL7{JHeh6kQ9bWsqXxp2LEmpdg)`UxWB0qhcro{Qi_{2%GEGGdeZt<3M zw`fESl(!>7u-YUocsqQ9fOU$7!J&>(E%%T<>Z*d<`>~g!vtOtBd#5_b;~^*i&aNj4 z6B84}pqEKIyPD+dYe$5S8Vy~JDs;QJtPN&HF78{0aWqQ1`Kp_rR$C>0k>Ms%ZyWR# z@F6VEOvrp(**RFXT-0t>9lWI?aqd?V~PrK zznjGU*YPgL^|_4&?v+bF{NGVhg};0M_aD3}3nU=_4M0`o|Kc+62X+9nY4pN@!m$yvNLi6>Z$bYPQr&xJBtjr2 zbb4#A%Mt{w{M4%5IQRcFXTh1x>AlnQfu8R2bg9frR4ude>)p#Tl7k7{kuNXKe{0T& zhuS8Y-}%YjVz%&h@GarTk8sV))x$|XYr&sSQ*Vj63V(jxGjK|2HjpfC>X-pPE^WPm zjeJ8r$OT-i59e0+)PmYV(9Lcv`p9CJ8T!oBDqIOYSL6cWoH>N-12!!;hMT z(%n#ozEU855XJ#Ck-vE@3PD6lriwjs{vPGC`Z$^=>-eBSCaHG7|Ftg-4l2ut0}nrR z@^1}oHY0UM2z4JyD%ZWci)6PEVYq4P4x$?a242{(K5MN`Kgnure*O{>Rnq7_t7*kB zA{NzN-A$`rSiTkbx3>{EO2Up#y(kJA5SG0}#PmxJV8enh$DT6XAjNkc$Ozq;&Z;Td z=r>A@w_?qjI<~;|dD1uQ`?}E+a|a_a9NLcsBrs{?j`b4%SFb6GSV}Q{pvZxDo<*?1 z0ap?x%;odGG{jG~-_rYeRz0*wo(#UjmtBr+Os}%Et4?{D^s{d?cE+wyF7of3zs~X3*W;Eyj$-Fga2~ zIO8Z;xGN9R)O0D4+FWyaN&e|fB_3A7`gU~2H*gt8urha**)_Ab^z-+ePcPRTcH+@4 z0u3tZ@84>#3PqX;=oUtr5n)T`ak;fq3XOWimrkkY{?3Xwzryi-eBKBmo<5KOaum?S z6nQf`tG~XFt3uq)*4O`7bG}{|Kz37EN{qq&{&wetEIomS)+#CDwANTo)16coJmRZ-4CC!iL;c;MQLHf%x9#`Wq z&7uXHx5E4AWwWG7{y3A{^cG(RSG{WswNP&%w5+e62E@jG+b3t#cuxN{JRE`YwdcO- zIkVGw@>#d@BFv%tJ4oCWAB0-0dbgI|ufW*wG(kKaTj#SI(+B%~M=h>PO-n0+d#fT@ z&|6o84k|nn>Y+~)0X{XKsNkrlN1F3fJ&#^atQ()B#gy;nM=6LY3+WvI-rMlO>!-?> z=e&m9-2za{OBMs9mF~{RPjAy-f(e@#7`7#a0$U%vl>G#KwgV9V$GKP9&aO~gcm zE*V^gKiYPly9C0U=n`=QZ!+g5yMon-_sd~IU$tVRxvmim+nh8SOHm+%{I(wvCBKCn zC>D}uY|qxPya#FuH*V~36!cG3xF6;T>Sm0-R%e{@#1Pdyn0UsEV|m(c_ik!vbt((- z));K0m`6OGXIf(SM%Oo}GuiIgTKQxbEYF`(UZsmrxCs5XhHkVOB1?TxH>Vd>uDVk- zf?pX?{|N=qm-OHH5h$gTT1BUmkm0NJZFOz53AqPElT=-bBitmnXxX-x8tl#+0>%`j^K;0J_3~`DY@$^v&UbQ(9{pm5D zXWcUu`!W&8!m-^`rIi2};ZK1sbSjG0?lRiD-L6c#v z@7eZ&SFLyxZ{h4lVHuUwZsa(ei_~ypSxdGg9jE8xobCQ0JS@9Z8VFl{yv-}q_4KVp znay+}ldbYZsZfldoBr)?Uk|OPyMq>_pO#axR8Ae>))gVOUu=_Ry+UH@Lxf+2<8-3E zzcc~cB-ep^gEu{U)x5N|Mfp3ToghU=cXK)4b@3z&7CY0%8|Jc~NY_Lc7;@JeY5guq zXM(Hdk}#Ii2qLVIUTd?VnnboEt^NJaM)pHh1mSVZwcOM^4B}y*s{3ljo?6Z>3bDOn zDDBba8U3@ooH^9ya8y*LSm5Cyck+9w^DZ^|OA>|zSkz^5sz_B&Mp&jT~u&U`Ety;Mx8Y+`Zy zaEufB%m}QB(utab4=y@>eV!Q!i@R=carU-sT{3sX#GFASfhk;eM`D1b5)jJXVD-G>j$}1-tAq*#%^^vansQ3zZ(SN zh#U|vS#xGh`W_b>3hDs>9vyF_%vd7tCa9>WySpZKIzdf5f$MxsNu!1}4J%zQ-gN(` zbe{RNNVlU~D0K~%{HDuYuse>d3Y7XLs$N{QYVftmww)?PrH zQAoQTscPwZO>apmNzwE45&^@bRWhP?9S$2LLu}K5(}DPVz50@6kHh`oJ3**-7pZ?! zRF9~2(Qqh#&O)I!tV?wbU8Q(E*&Ub$gy24&%L3DZO3F8O&xg%Ik|-kNl0ywmO}lW$ z=}+C0oh@76H*?o$+6uMTn@#tr8h-wv{O_dspjclO7ovUTmzr2J+O8_;$}f zn^S`HtQ}7-)@PP)H1fBz-|GUELMoyMv8AnTTDbZCbIE0PQ?o*rGp}PkOV|SqEv8Dt z4_M9}j|1$iL=>fCbJeuX#2N4?Jn}E?ut9%^(qlg#)`dD`JgbC)xN|uAZ`j0F^fy0| zn&k9kC>G%0SXH~c{xK)!n;^wM8OhCPMXpz^S|ccUZj!lCL)T96v>~ms#DMx(4yWp_ zYpEpN^0y*QiGlE5JmQ7wsJUzs#9$X_l)B~d#D!)L>+WK$0!oQ_+cc3VM#O-tx7en8 zc*T&`vVRA9mCRq+VI$mN7VjSa{fu)39|lxROiE2>;_7r(4z=4S*6vD_yu5jt z&Ap$_g3I}Gy4D4Q45?Si+EuX%PJYGdWTJc8$!XUPxZSmqEZgd7a%5mkZC#EY-pAR9 zl-*6G67sq?^7N(QD|}fhG&lM%(kxY|Cx$dN_-<)N5y0 z;+Wm}h4tyG`}FYp7(SUwL$qn;Wd1E5Us%Lm5~d_8`{j-L#z(pWUP#fQ!z@36qGHhp za@;=^_0q&Ac?O%6(#rkci4yswvzZ3kCREy8EbzE`gdEGUj=G7b1wzXwu;QL7k2ULIv?+2}1&WgZ`>1miQMf)6DxS zKdAHPy;@XDjl+5&;Un{Tajr%7XQR89mqybibd`PKyFz}-es%Dt5^WD!uq2y zDSdGHs2%pqxK53r^-?EzG6`EgCdwRqrJgbM^s}c;!RMH4kZlg(argMGr~P?QhTqdc z7mNv|eQx?n6KT2oVE??YJW&YtI>D62v-xtJwf>-iIa*VDV##qnGuYv@79tZ41#hI` zajE6mi=W|fA~b&{93k~fyI5$2Q8=Vj^}f>LS-L_R;F6iSI@du%S~%P?0c9hY1wh&IuC4l`*k4j>8GAIu92|i$!vH%V!O}_0_8=2gSX~fCTXH#`-x#K2_As#r;*lp z9V%7f+p5ZaU--&SjlJcI0)ojNm&4ggX@&B{sxW5cfPh|d9BW&rx6XKUV+CQB zFHy|f@ip&HrtJQV-Hh81SEdwyO`j9j8lfmM?nZfCVdJ|}Q8WhRA4DNW_M_`EXm{tW zW>4|sO}h(SFMNMM#kRjPwMF5u{wCJp#1{O z%PuB1sZo_>)6dbL4>!IXV+}5Qu7nf+Y#mCWpRfv+^`RK}y{75isjuIWUWZgj9Qu5~ zh73p+3yrrbQm%xu*juonv0MuB_5J&&gX!GuP28M6V~uyHVT#&cuFnTcP%zu4ZvuV^=pgy0Kl z50`$D&cQ$~e)D|Jd@|}>=PP86e*)9q$eH?UA3S$MtT(2=DQo(E9K1B)rpG(Np** zrtsn8NR7Iz5zN_R`%&*Nw`_COZ4k-aBxYz~j9gvGsCRNa*rNg9I~~^&Pqm)fWLj2g z!zG$}vkI+b^^aA$+@qpBP(|Wcxt<1YpF;{fFw|$VQ9Du}_`tO&S0`p%%wqYYQc5^0 zX4am~td|dLxX^ggE8Zja`^kEox(XGG9;Uv(icM<#5l#cNkuNt$TJ%|ERs%Gh%(wO_ z>n%eu&R5$ptR; z$y5lgMhn(l6K6O*$EY({Pc+gHo_(*ldQ+CPa=%$8UEo^dZP5vcYpPWmUGi%3V>%VJ zs?b0#h5G(%vT1A8 z|GECErU<$JzADQrIhTu#ovIvXDqm-|n*c*vIsT1y>n={42W2Zb;At3yIR}x*%)5}& z*OBVA-jS(J*m7B#4@Ul6FV9gnZJ06>mw)-2<9Zu>3TCg`mBo``C0z}3OU?bdwXx8( zn`iDMdax!*Pm?{F@4jKX>TK^-c0;6n~V!m6;VYP6+-sa#zqnGyOVVS1F<*m2S?$N`I;-_MP+2R?e?RMm_ zO*t?kx4K%z2+qbrRzzFVv}U2_ko~o2o%Krx#)ZuQ=XRAaLIurSYh}#M zu!KCm_hVZ`68#a{N^8B@xSA0MxaH4*m`u{`&30M5c#J~SBfYD)E}EY{-$&GxCSxCF zq0aWX10YSk2Jz)JFa2P_?SY2O$9WoQw(GFpysw4j_$%q3;rGdr5>ZiWR1^0s${z1* zNoo656cQxVZ(9zhH9*f3R-NWdxljPS(DOCBmC#RZh$fA--+Q6;CGRaW#AQ#<9Co_{ zU+=?8zxn##|Evb;wi6#~QzV)TsO<_0eUi@JxG@I3P%EgPAizd^! z8uCO<41gh&Zbvggd3>$!O#Ri)hG*40zqo=MD#Z`j<7E~d2vL~{%|!#`Dkj(lBWR1D z{MfJ4TanWRKKr?5xwu-*`(9o}=mP)(P=!rb9Sa;>V>nt==)YK+>q|RvbBTN5ieJg;A|nziXJwn4xByKc z4BO8DK=YUV6;B+6O&W#`dj=^B%D=a*iPKHjx#&JhWz>f^6A56SmZF_sF2sD^Z`~GU z>In{YUGxmkxbk~;Q;!@EhUSh7sWsaZMNj}`MypBYu+GOT>)Ev$76rBjfz9c^vqEh@ z0Q${M&MFEfwy;0Mh2?rX%Vx#vQy(9~U%{@UpRAP(DH2y!=FZrS-wvoJ!G1X|e3ge4x-G7OX(E_%6ou=5s&K&IW<82gyo0oL-S47{!qm|Glk4U`8idL1f&S@A8c7fJ)zv(gDd&6Vdw+aT-WK@^fCnU#3C}3T!0~w zP%|SAM|JwgT%tWVNvmgdSDC3AYnAG4AGvZY5oP|CDdgghY^d-{5 zt`lFPHQ>GlCnckepRGGzLT;RWANg&EYTK4P>had{{{5)#-Gtp_!pr5bNSX0Nu-wC? zLsR}B{;u|7>z{Rbui6`7Srws*`tIl5nec6+DUZXy;%_i)2X-fk~12d}5V{w{k#ymuMZ zb{qFQd+qZIHwD7X<0R;|Z9p#M!Ep5S*EX(#j;inElX+!-mEPjF^|vZigo>PR@Yqo_ zjH}p2?8BkU$1dT2kuzF(W3jp{>)bXWXH_mW-`UEdkjIGaIgc8bVwZp z?DZ6%JG(}xq5KNRZuhkn2-kANb&_2BAT-5;x4SYih#6brT1FBn%#j88fb5)^=}?Q6jtU*la&Ee}v!T~&AsTV3T*QRx+0OP;)DK(sGkKW-@nLUP7K;a< z8Z^(9hcU`Pk#>hG@$tD$Ak@uV^#ed|hPo`l)UR4;e%>dt)7g_*V_y8-FxW~}KAq$X z8}s0AxCo8CznDIcwuY9P9+f?Q!i7gh2j$oFX8Q#<1WTWbQ=>Vtiuk4CoJ&l69(rRw zim~@QR|@!Ew?CN(jSGBI%zw$xr6(oIptXTA894`&NgCVL>5pE!qu@c$)!ZkrBLM2> z^=Um~G&M;=M>91hr>7&oK$a7~dd`bVBQ*zatN0k>58n!U*GQR;fXeFTsV&~FZJLS;`}UZqw&EG;&$26MKQ0{(EW*XDEV;3aMv zv-%^*lAM$(oTb=O-#mj==x*Wd=A25dTvGClV`ceia9qtWV8~3*U`Vl!WeNV z0)P-s#u#EMIqjKM9ibxi&wB3-i(Z0jX~YqFCsQsKgsa7Ufd}G5-s^piGWq|9x3>(6 zE9lxqfdol_U;zf#;Oe3Ne)Z1E%EG#RfKojK*kj6{W|t z{qD7;w`dmrN)ya!XU1oy&;L3bN6p6n!hd(hoe8Rq`0x;ui?bxSXiPy&LG=^<9^-y3 zJ^gq^OOE$`ZF|K%*yFs-Zadx5s6r6rJJlA`>Qh)y=yK#i^|NCF$R-m5q_Vp9O5br~ z%2eCK(Kic?FTi5@x^X%odf2BkhBRT|ZLjxYU$E-%Jc@T@LQYJK5*8WINeOlfUA-~S z27M-17auW}har(^bHNZ&`ZyNNqs4B#78m-nA*AnDjnAaCL{IZ8$8xW|}GY2G5a`LF*|^#qLpJ2+0z9 zwa^9cZ^BVDp=4%?;gQHh{$|1s(xidgq*_Fuczr-Gt=8K@X4Wza4|zg6rnLc+(!u&n z^3`G~@nbyC!mr~KIIvA#Js!@B`nWwN<`& zD|09`jrczbak;{;$>w~ozNO%r7&TbDIyLi>b?l94{21qz$Ax%2W1(K4`wCM>_3@Jx zLzY_EMlx$ja2l=qk{}5JD8y4p!){5C(CGC6cZi=RxYDS2s-`YH%11IQaPj1MNK&~$!3!?{ zxm!YXxRJ|y*b)@YGoV+?)L1cWd|BwlS|1$W-E1wC{3I1FWFhOGHu9hLVq~s3Q>7ZG1aG<60#QIq{(Ha-W@`JGyG}M zPJR7fWQEe!o=npfZ57ich|vh)Z9jaGnUWod%kfv6O%~&i3eb+(igZxUK2z(MnP7E% z5gMqwmf+!Ycv}>P7lZ>jX`oD`w~n$j!L+_5mCP3=14Z~?8}7@26(*4#B{FsiuZSC@ zLI4)$URU5y9L@yO)$0V`gk34}%v8|x(HRk0`0}MLtJ@?X+mgW_S(Ztq1GY)RVK=&4 zt>}j+OI#!+b!`cIF>weOS=ZJ59t6YolXB&N;V?lg8)=kUcOt^#l83@&5q9nRgb z6Dhc#K`9U8HIStTC_LdE|7K34w7+6*>l9v2C`YEW-#2#Ru(_JRQ0GjmMU&jB23T^K z5y*j={5U+y0K$PmDy7!G`7T1}j~uA3f{U$6Wh3+1OEtUM09labb?dOGWQsQHn4NCj zTg7^R{eGUVRSf>k)u`4`Tp1hh>aewxVA?3gjp%p4wje!b{!};85pRPJBJMY@@h_jJ zZBDfIbnKQFPVE$A?!{K*z-o`xZnmKm&l?XWNU2<99Y3sm!n$eHdE+wtri)}gH#PH3 zg<+`PA4+4VSV*mzF5P48#C{{GKd0{@sqQly5^qL+4(-Gg#^^y)i_J?Gi++m$bIP)J zEpo8F3C+^W!{rPXJgqZkx~8YrjZG&PBWS49>(k`)+fv_3<*Qq})M~oW(j5^f z<69f`)USRPIbH2WP}1|f^L57axrxWjF?%Ct{dUA@qAvmF-RR@Jis95=noKR|kC$!7 zcN`F|pkw&W<(SkeMj5b0|8}p&tu^`79T6iSXommH+tO|#VIX{MBCjCeV+qq-YitjJ zH;8!VnO&VJE5_?8<5kE83&`HaXdfTFKSvBKK{=1~B*`E=?r(X(lJCJ&PK&?egfXJ*#sR_Z4UCgf^# z<--=E{I@~FG@F8T@+YHm&1;G=|Kau}(lO~KzIUVfXQ{iur3&7tK#vzDvt);rU@P)z zH#j1zuyw%-rE47iMQ3ulH1N<>$e5uAj81NxR(q!|dbHTnq~7(s zb-PC|Dd$UdF~_fIF|*4|{3lCENi-Tnx1Fu1wBX@srF~O8;zT>bH2&l%b~k-1p(6u$ z-!L;HZ1P04m}13xa`4Rc3!)_7h{>H5TNSfdPBtcElH4987x|5|)T%MpacFRvU+yDF?}Fv6%m$BfJID;|Ji z+~1`g%RQ1d>_dC6)zSgL42I*v-a)aZdS6yS>^Z7|Xx+uOXF<#Q%keZcrebaR(|%T} zUrvms7&L3Qiba!(D?gT2n;R@P0@T_nRKXV|Fppl#Tm4Lv?J(j--g5kYhE2>%w_F zKNAf_6TTi%3LodEs@_~_pDPXl>T=Brw{3G-<*lf(7?*Vw#aw~(TPBfXW&T{5?c$#^ z7hd{RG?LD@CZUi;(HU*OrZ1py*g?n}0j4X9wQn2DO1_I`+v~Z;EpxXPn#A{X+DciLA9uZ~&jl5P(VNfk;Wxzdx#TU2Ql?bu<(z7CYAs#veoImmr>dTL>x*tPXM zW-G+hUK2oVcNz`V0aeK1F(CG|i3wdFo(`NDW83;G`TrS`Iw@_mBT+QX14Fi)Hr{Um z%W@hR2>UJ1?n{#RX(3SMS%+C=8*|Gq_>BDrt~tN>-j{h%WM~221sS=&a`9`P#Ff`LwZZL zlrFs^$7Z<-&98f;Po+{%-w*puYCiZC`!w<}UG-sFkrNU!aB*ExqX!`Olq<&Qeh73n z)66Rkn-xa=rEE07+iI^j(oPLJLYgRAyX#lRbNW*V_JP4Rb!9&VXY(s)j^k}iL}y%Z z)Sp^C9&pCzz>I@d(*J?#JF>gD9Aa-zO2GFGm6K2yoZ+sbnnVzH#p-(nolMCy=O>M? zB9#)xnm1+B11@QeaiuKX1JEx#FR#>n0|Q<+MHMg1LC6Bl4-u&iR?Z|(FX14EhPAlk zx80gM7n@OS{i36=M}F}oa5~w|3Y$M3HBTYjWOmVqbGFlW0r_|xvYhE3?i11ZeZO~Z z+P+7?Ks6R3ymXchKj3&IkZJR89Cey9B3m(0!RIAmacUlmsgXA=1zqC!5Qy)nud~P+ z#iCN_Kn1qmeJ1 z&(^am1>s4}Qa<6)>L8h+lN`G9F)93Qe$Oo?hxlbKxU@?-dE~yhf~7iY*0Q;n&B@@U zjE00tKOw=7IFlWQ@TTXk9|EG?&;|iwBkp_==ffL6%eWruuWHTrj3_D)t|)zI1djTREdX9-x5P|OhzJQ2jKsNka1p?yRsF(o)aMJ85ujNpy znZqZHk;e{xq-ElWSq|8{w#Z*18ue@oX!MGqO&FIx+qiXh7wT2{#e2`+R%d9IxrK&F z^F;kbx5D2M@cf?Cn+UU z%nE`hiz+dtamFW$8?tkGTLRjsG&*h0@UTiI zd`(XblX{OP?<7D(QIwyWr%z3$xV-2|K}0|I)jZ62m+?Hx&54HpKCmCrr)_J1P^~OQ zyov7VNt7+%xOhrk#i=x*e=Op_Skg=jwpKyRZyXve*3#s`$%@^?!yK-KgB{7?n(2)w znb*PU*L_)#ma3|HCt5Spnss~(YWw`x6(95EE9o>+A$_`Lq0wl9s{5sF>{XN5D$~Tr zU2eyNa`=oY`hid)e>m0e+iP70+MpXt7Cp=7@9llAH*H_twa-S6Ui91Dv>%+}vGzmZ?p>!Xp`oa0n<}&4?w)0>|>t z*Ub4ZT$eM0I4ep9$C_$Q(XH4t;hB%VXijFXHI)K+XG>tu+H7XFKlaG3*+2A3Mt_O9 zYh_sg)`Iih70td>8fFu`nX{x0PjIxC#~CXxG<&^X{kj=J8#wjWr#dVCJMthNCW2E# z9BAma(!)yJ=a*I;DFgvRoeG2I?z(cLBEM>rX)%%w)TiKCr9C{fKI>!DbV*J(ib#Pd zwQy3vG?}UqNen*6lu|2I64wp+l#IcH=W`xq`!SHL7^W&V{C2m6eOJ#YV_&giV$aVK zxG-t@o`jhavHQP-N(pbG)I z7a(OsAE@o)BRYb_`RMc-VGv&(WNgrWGh&VAGAK=Q8<+`CtJWmtQjSI!{$V;jakDXr z*=~ZaJ(;e`swnxcE+g*Gxx94c1dTc+JIxZu7sHL zyb?ZuFv6qruGF7^_d|Iy_}e|-DdkD~Ig*+isu45Rtoz;a(1}JDOI~;KWQrSgJOdfR z|4jOhQ0=JV&wh_=p_c8V2>Ff6+vNY5^I4h3P$qPy(Y8@dEqrx*%4jS&DXq1k+)Kc) z_Cr_-t+cL&v%qkfRZH=yE@QRG(w#K`K8yI1kRidf>qY&+tpKA~_f)Lv4ww-ZcW2e| z?VO}1;Z`G!@aSbA+5J4jclsAwzplpPd{3}p{@5g)CJt>>E??MU<=sXI&B|I|fN6Be z*r4R5B^%JFr)SI^*KnDWt2#;3x%1(vzM7VPb`%_lA&Y_zGk##c$ZhDTt{rn5ksmdT zP}JM5LM`{q3ktAAV&6v?3MlQy@Q9ag&dA!~d}z+0*;E6(Y_vp~q^UENG=}^eLL0|! zIED)W;eot5y>bbyk(wx&M8cSZK+#?2Q~4eXBtg&fC!zSqs#I;6XnU6Yem_}XH{yX*v zZoTtTW?70HaP)xP(p_iDsLl+Pj$k!KO?`81JHdfVcBONXpZo?PZ26H5x4xBWpsSA$ z)Dy@o(Vq?u9AXg8H${`ddFNV^Dq=UD6i-5JKeL}bWX4pTH)koXt^70XBAQXkr2iDH z%LQ*toROA}L&6@LOZab@`d;*t^S3UNbEv|{k;S38LV2^X3T{5LpBCh`L{38=Dtgt{ zI4$>Pq1cS)C{&;mJ)VDSwImPF@nq*ofMY(Dk7Yi}8QF~VqZl(5qGOZDfSo)8w(Fsy ze)N22F=jN6BB||8r(ZD}q@;|1S7azd4MfQz=@j||s?rK>6$VK)iutwXvB%luAcYJI{gAZdLE8?{fp|8{&oT#o5hh209 z3}E6$Gu`VnoWg~v5=QKID@>MqQHs>ssrn5R_lIUHfH~lQ87x(5_26jKsEF+F!2LHh zer#Q7^jKBh-5&l%HfZw}uO5wV@j+~TJ}rJCD{XnY6@p_7X#so0{6{){{CwQe|CJ#g zqP~azQ|A63iX%cV;9rIBpFS|c{42_L|6i=yLvaT69kjhiMt6n^L7@UbfRRvqzbTbB zn=o-`H|2$hb^mx7VJZd)?3%!Z0Ahypu>o%02jGeNe^gL@`-asM+yVz7i5Jp$HMwhE zH*Cb@y>9?PaXVu^%g-AZ9eu+~4Y@(C&zABYp(YRt35?OLpa!?|@LRnM91f&4!(u%|vB4FF$|i_TBYckC;9WECEn0HZs!kr4*>TGVFXq z2b^0Dmmx{cFM|nFj7~}{$u>(p-B8G)QMzJL5%>dC{;vw2=}3)gXbInjL!Z91tea8N zWtqlVm11h?sr_KQ>`*#oB5PrkDKw7- zX^fqY!4dVEyX@@wu2U7apC~pHpy}%;ygGUHMn(CmViWZ`9W^~(Hi6Lfep&tPEcq>T z&LSD-l@13B_cAx1H10;H$H@3NOG_&)ByDc>$~I#?z183IBeiGez%K)~&-SeTSRP`I ziH#yn9yZx>ZRG;>CME{&n;&ZE*V^-X>~O^fdvKZb-2G-A+dlB7+^jvze=BCxcSqX) z!{{4lfz;7`_9TTve>VVIJC!|iYt8_k{)7+`!UD?NlY?2Um-CXk&bf z+T2NN2c_<8M@k>WF;AXEcK8(6Lz;$hnz&G}U!j51(lmivLzBfq%JR3@E@G(d+pY6h zHnj@Ayjd*`Zz}|J&}po+h@cVp_^y4gJy>gzp=Ip4FVU0R7LR(pS&80uAz{iTmM}PA z7I+O*aq<=Nxer~py{S4qw(X{0@03HaL6X6tP=zu*k9E$gBG{PEtIgv0%(as&dd25r$L5RLSu3I%f5+_vx&XrXF-ZL`J22^VSnW6#5epv zX&f(3C;5zxMfB&W1X10Inuj*s@MG~{fei$I8o15cPZ|;`)&`=Iidm3Sa=)jCzZFMF z`~6V(SR-1Zp-Xu$*H)`+{R27Mj_LL$dAACk3byvB-Md|8{jup|KwDWdQ&eog?cPf2 zZ-x?)9&tJDwDkhr2s+v}2U9w4LARg-c!#O!7<6bjsnr+Z6jC)Je~OTX{2;q5^ruodjD?vVw-vjN6BQp`j)S!c}#Q5K&7_=mj?vQ4vY=g7*DU>yHE*K zc)EHTFEqvA;ig=?x*uvsAA$Gru3OU?8f&xH^oJ`EO-{`dj!ku~bjflZy#NjP^yEfN zlykox+(FfkJ)jIc%YfM*S{p6)bv}ciixFL{>7H|vYb9kiWr*n0)ncdu3BB;)r_h2S zDM@B|)%-uy4-Hj*xL5&GL_VWAv-#IJY)!9h#^;QIT#tL@R&wLkC;ixO1NB~Mb~;mI zcqDRU3LJTIzk(a@!C6(R28a=|R;aTq)C~h^J7&`IhP<84eR6--Mv;?M$HudZMOfUp zX-s@cZ`0KK*0G&}^mqD0JoxCz5;ts>XFdGpZ4OPxaCAA$NT^Za-`7sDWaDNidkuV>P+~{S(h(by3Km=ysd`M^xc9{QE#!erh z1=~j(0zhP=X~DRYr;b05AV~rhBYQ?*+0z^)iG&-7b!4}6qsp)!c0tL1utQ@6nS6D1 z5Fw+gW&ZvO{rAX|gZ(yfYf1!2ak77?&0_t{S;OVrfR!APU}t=Cp>g(4EuDtrIY}x~LHKeK&{umKzANSs`At9sR3kr7siArRy!P`(%#OyI=S65M^w6OBxPWJETHBJIAgWi6Eqk9dF~~>(;y( z1#W7cL^0Ip2=>3QHthB2mi4zYs6Rk|tU?D`NnHzW@>rsL0~lp1a@Ay$i21`ev`^}|1j z!pjom+)(scldq-8kWy!0q+fH!>oh`S_Y-lC5OsMM+@@#vGZG)X+t2PSQ(21bV-!i< zs{u&(FJb@Fm|l%dX?*s7-Nq)<(QUf5)Xtjw1sCqs7w88y=8$l%;+gVp9lYVLY_qt) z_$sKLyOa!cGTM0?Ud9!}N9p5Yp+HlgT~BOTMeX`D3oTLb=Craz{FFAr7TfGoqG z*6%z@tj=kEA}B3^V;7yR`@E|U-Gz&j-!>vb$o$`EJwi|L6c-nL^~{y%&NS+GJ%f`} zI?gkcum{KUvfO`bu266B|p3U++? z5QJonL1?5dogC^R#$LhYL2YDm1?(d4U8SqTZZkB8HRUtBx(*RCLRlV1cT8TLw>Hi= z3yKZ{7*OL4Tk%y5T`trz;t-^4PN4j!D>8GR0vTamVqc)_yo!~h@2kR*ldbm8exgoT zu4WK6sDf^v586M_@utQUihf<@%xslfnA=@u$5}l_NM!ZYJzt2YF6XS&`ItPI6LObJ z!RZ7ucCy;A8uL&#G%jR1ZdJ6{V7FA}ir$msDe`5=3r$>HXC)0!>OP@_Z_UK?c8DB` zCLi~$hRljC9Czkorj!9~%%mP?H4N?bIY68Q=7Zed?J`yy*|2loNF*1%hXG;WAi}YI z^J#P$)*0BKi4ykpvp|n^nU+euz;APZBKB7N=qrYhCr|lch76 zU)9kpf~{UvylSq^)!L*v&&uPR+{U|)1=^ppa&zJQ57?7F<&>F(=^EyO@}Y)ypg$Uv z6b0-P#+{0{>2DiXlQU67Wfg5$W))ZiQE8u&@*<2P%wt z)%+fBszE6Sf@mJ`F#pG({>M_zS?{O9VS|X42Xp0Z*&Q?md`il2+y3xBiXPt?oe+Th5Toy^c zilWZTt~@koZ`!3qQxQQ?LttP9LpE+SMkjynb#A}2YrG@QSE`5WyzY!|Ua zjzZGy!NO!sKp@LVj>T_QF&eE&$__$y4XbhLKOKiGReCR();(GroN!o<lW6HjB5d@`@gw2LQ!O zr!Oy}B`40US7K?EifPC+?j}v4AyZG;QG>T>|7{DvMfOy7m{>xf-IJ6*a@ZyU8JK{G z!47wlMMM+qCJ~SeLNKl3w8Ze8uI;nTvi(UI1{1B-F_Hg`c3X4|K}Ic#LnL{OEM`%X z#DANHEn=(_MMjebG;pFX6dJmOu}-s+ zJ~I{#%4Cye)}(r;}QNP1vLXhqxl)E$Yy zT$BJ2O$7I{rJFOQuf}XSdS>vZf#qatjEjOHpfZ!$3^uPyXKbVk>AVZSS(QS8y6 zWB%#Ku~5i{-x+f#LRc=BK4}-TM6R1*a-uITiroc>6zgU@qh(DaL<{1IJ^yZ_j!_{HrC3M zYMCoII~N}pf~!JY^Tpv}SHPa)_XwGb9icW^jafp@wdl-*?9#%tk;kgb@QhRSaDJ5x zSH3JG`~D(f=gOwqb|%A63ukruRdcn0XZiBxW_T#dvj-32f*G87Rk0{GBb3pgszsab zneQ8mI;9_eJ-jyW}}@s3Rb@hOxjOPqX3H)mk#myy(Lbz4swY zHKWDx%wkrTPJd&-y=)J8#e&)A4xJn?W=1V{>+8ZlTDYpa`40UrTjyt_7*}R8$_GmU z$KBOd9mCJ6$(tDQjpwbew>hF;)(PYY!wXMCr#}n<-Dwj%w_*?YsVFEO@|R&Z_KA=e zy!MhWn!p(RgK|!n7FC>N#k|*uCd7jV+()P{LC-#|7k(wZqgMmV6}&bKwr(0fi7r5k z1d$x%#Jva5(UMcJ{4=%3g6|^OWUv4{&!RO|AC_GS zWc4y zkGNl_Q#(#KsP;eMu!C83_>8vZ6w1DoF2z4tfg@!9%6gZWwR2Wid=E%==Sd9O0*&m$RB3OgD3HM}x1lj?O{^>EvkI z5ydK#r_}o3Siqzw`(-cXwrXAj!$IVB(4uc;!6)dzj(Jw*(u1R+N?dr9H1w!|6L3++I(yf z1b-P7<+U_~9(7o~&YXmJV2JHl=+X`_gz9JMuC@viV%HG2n!#10sy-Rs>&SCt+lA(W zz;J|FXg+cnkw49#!PMKfu`NIlfx0VOJ=8TRn8L=!rb0Dsv318f6b|P18~h@>dFec|FD?@HDR^#wE&8 zwW!5DCH@k&YRhHx7Le#+n*SU$0dz|T5z)ewJ>D*i<_cIa#Qb8hn1P`dTo`$%OrWcw zKVMyF%f1@VVl9~YaIYKTYH!BiZ1{R6#bi`hTt{|%tYm?jguvyOB%vgl)^C=Fk{vah zJAVl3O&f_(}A~b zV!K`Iu6~`7dj6q2J?rR-;Qy)A|Bn;09~o&44RWHQ*dktA_VPaOl9!iu>jk6`lI;IE zEX$SmG&taAXJ-smhUuKYGS97kH{AO{@XCi4T$y|JdTo|ElqHQtFDGSiFx(TV|KBTW z^4h_8CkQKNINST(nBv19_+RX%bK+F&JnX+~{*U|riw8^p7mur?qX0eg@yBe2CP7r= z3qA|kVcjiDFUBFuPYj3e>Nx*y5yDd#T#aQ}rBH2seiG&x=@S^F3OLu3i-~_n4g7DN z4FU$fOP($iE>5l`dxoi8g`NEpMAE)07nk~N2Ea#O{Xs>FxI%`k8r}?W3geHyqeloL z{mI{?W>G_pk4;QV2JE=$a0{$+19W;Jix=;RL7d%FP zY;CG2ejU3v@qGJXWB<~I5r(`8ze^|RT~#epF=@dwrF*}H4L4-(E~TL{nN`F5lO<1K zvD6XKA|Gmq@_}Rs8(0sAC-Ze z)8>iSoIC|8q7}_Lkn!gXR`=m{*HstprHs$AsIqH#1!EUYyf%+-nC|D{B>PBP1dpu5 zRhf;{&pa%$4^HZCp7{}^8rfCyq<(~sQv=V=R*e_hA$)jJ45Vf&c73gZNX>F<@kQ01 zK|diW@N5IiT*~TKpKGmlj{OY*A9A7?`R|sB@UjYW!uoV@A@Wc%4jL}!#Dma|$)wlc zlufa@J+3FZuARvWvn0O!Abz9mSO2Kn<(1NuzRYq9_0+ zW5t&5lL1CE#f7aIgn=2o=0_mN5ZrL}n8^G3&HTui2d zW9+d}`g9f(G8OS{G^<$^i%l2Z=T1327fuwQ6M`)LMD-!svF3|A# zh-C$lV4%cgk)g1E)4~Q>Yj&;qbY%pI(d%H&NfwN(aXhP&Nfoub-%{NH(Xhfu-5ZFnJzmC+08?LKQu~FY9D{x zM6dobrd|D}TE3|=K#qrX`NCwKyZMa$!zO^Z%#CST)=QJXBNa8rbM;)J9=|oO&P(5t zO&d%vFV;q2$bf)?024Em%V5Nocy(I{BiAYT%NZH2Z2?-&aj?$n*XC4UR*NnJit)Ec zlawQ3f}^#~B(a{lug0@UaJ}z?-e$svzq&zI3H>W0IAoSS#d#9c5j|5DV;3V2GWz`q z5%XaWx3e7~P4Vv^b130ulzz#`e?*P7nz0%zJPRA)MqO%6P>ZlybK0s>4$=2qH+dD4 zJ6P%r%M%Y7k%t4Ws`YHeS}^WLOqNmqwzYbzNl8g4Z;7>{+UtSb2=f2kf>@I2!FYX? zW7)GV)Tx)NQ2=(@P2FgkJ7crgZpi1emUhtXFH^E$!}W+Pv4b<~aFh)a~Dq+*74CqR^B;4)9 z1VLyt#l!6axIN`#yBHl@9{MWCqa?o%lF`X#qwmlzkb6|4))%Y*lPrWCs7_2E*6fIC ze_E|Rev#79OfY;jbf7zFP(IUXDJ?tcPJP7eTNS$n$&cd?S)Z#j+#fi!#dX;uVl%i2 zRvdW?#_J|wHyLhMD@_<_=DQ9}!g?o=rQe(|v*l`T9_k-^VvhGUgyGh3%gT@#9jn{) z#vjHx%i_3mV(=Ahz}|R=X1U4Mao=EThy<8%(G!V!b0l-;-&dw6V3WUV{`7a)bSqtI z>^@rbylUM02>eR;D}(Q~Ju;t)1;EHPNc^>Vl9QY~J?^b%T-_a0E-U!XoWs$;3x9Lx zD6XpR`6Oh{2fQozBNKC2vV};&pN`C0tS+Qr`Rs%*jz?3{)bT@s{3q zPwo*pL}2r9ho?^pI2zwSRPLMW>+Rd+9Pg3%M+%hL3LnrqL!+}pI z2vFAN)`2-KX;B6bwu+3&n_J6vd#UL^ErLvFLnh&0k`Zt!z46yvI-Q{dwD5}WaRPX` z2)Ov!?{{l(Q;t`SO-fX0Cc^-3-PSuZ_>lS~qB^65eY@0#Mnl$G zkQ)}nc_a_OcaF-n_p&*N)8l+N&bT~voztW-b>4pE?2{0Qp{eDNebVFp zo%c>v9{hZs`6IfZcUrwQ3g1~o+N32l8nokSxvMMr%|uLeVc~}tUgiW=D^$ zns4*YXF_|a*m!{pYZeTSLit0BUJollGCCSxtGmCZZV>46b>*1S9%L0f%32#_ZB8zY zJv$;qgr}K{pUJK|T-mil7ew#yOmse$Eq`gZwKI#@Xbob%!$nL;V&EC*E3j(e0^?F! z42{w_ zlSiRJLf-sOQ5gJSKbV>7!UI!{M@_GKQZh<`=$sjnKp_!RKjHjQb`pPuMoP7$v;ngd za^i9oCuS3%stFGA2Qe0d)}~~250}SicqP#YEmS6fB=616DV1m7K^6+&M?~QR1FMYu zT3spov3_mS!F)|pUu=SdL9WgSi3}W3aOKzh!8p+ZNfo=;AhX45=W~PS!wf}LCy?hXVFvjqp5&+BuN&HMJMtH3})wm zV)}&j;p}cr&_o#$gTJd0;HTY_O8SA_{)VQqVVRsWYy3#!et1Z2czB2 zZ~S}>o1a?x#JR(ZGvR>*bO9wMoe@V$*Zp5b7-<8L)Itv$w^jo*1~9+yylK!k*nT#g zi#Ta|3%UE`ZLY3szA%{+!TgTj2)+4BT=m%Ufrk!L3(tit zu@qM0vwzi_cm30cCjVoB?flVDT(DsaPL%UU>{&Jhb=QnlwR^TF?0XUzjv#i$_nb;B zpY>^^E0d0Jw^d?&PH-Mybr0DVv{|Lg_5X7L>n;BW3rZ3Y%)l(SPIXbS-s(11ZB3WH zTijr-I#0{?5?Y|^!PWY^iPMwwJblCkN z8>jQ!_Md0ec_6II<-Idz%d^T!;|-?WQ4H+wsR&OfP|VF%d>BLfwf60sOGZVst zua^cm8ipSV)Z5NKfq?VJ<7MlNj5%azGT(aLmB*K@=KORKm9gr)v1!&mFIYQHgK`2B zOrbyBrZAC(4$b8kpnsm&>T{k%@$F^v!v)e6zGYH}_?tyVyy1RB&nQN8K7)+)O3k7{hffr7Yh`d#5nIa(1K%3@Y5 zTOXqXGJ?Hs4iPmuu#n>Emv5do+OPK~8&?NQs4zxSGC#_#}=khW+2}xKs zpWWoI4xhY-hsKH~!=HT(XrBUjzW8;DhbR=N?oc|B1|SnJct*vDcN%ZT_GQw=q8HMB zm8pCjH}GImq9R}KM-BbvXkWGO0N^lYJ%|nF$@@}ed5VV{z-F1shQs<b6VeuzH zi1^MAWJQY4Z(gkI>{XI^Q^!s~;{t%t&Zd>=ux!jR{|SBs+oHNE~XeSxl1qt$5o+@?W%*hC>Z$d}BiV zXj6QZFmmb%>E#TwQTm;)u22CEXhz8{n)h&a11hj$jS)wy+_JBSQgh&7g^y5>K|OIJ z9j@7GX0c!pc_}xV)W_5F>yo+hzH!lYFkPS7aBjS5@vaX&|(OZ2LKrs&v06X)7k6Zpi4q&~adjHzhO&>?@n_wU$KhOVmbOEm%TtllJ z@oThaOb`#a42u5%E{OkwgG}Okoy)qug0{tn&WTaWb4z{wLy=&LKBznE?0)OeZ~cIr z{sj+%j-#+ubp!e;3!0tc1uc1nuh7tWza1BJoG}-43p{;((4147A8 zo33x_-#!uV6Wni4x4!P+BcMV+7?|L4y~w;H`@5?N2)+{x#o=M-h^M9!mOMvo_vwX0Bmr-&e&4K(uU&HJxULSqudsJlmUEY zH&K#&s`_npNFQbjCxT?fy!&TNh*|Iz0-{jvCiYd$=37RK?c>SehZ30oRoYbr#i2yY zMHUEw01L@N2rj`bkYGWBJA}m{Kwxo)#U;TPcXxNUMS}%*3GVLhJZ`=J_w%aef2yYD zOwH-;(|w zrFmU`4os3}Vjbw9wX2|AzWDKMKY1RXg->TIqQSert?Vsnt>+$a)ibSMQm*oEIm%l#?L5L+xTWNdd{r`oQD$Kz+1JTY0Id;O`*#UD>sQ<_Ru%hX`>xv ztGAa)HMRqJrGgeZCsH91$b=JZ1LK{`;U=^_%a{3Xa0XsW2KpAS`wOq(G}&YQE-!H1%H*xu-YmEGEjS|W+kM1=}^DyPpZ0p>N~@}T+IXk z;#*79EqizPE43MZWHpp(W>53|MSB>1ULI8%X;^)vG@?@ydnrZAu$|l68l)E=pZK`{ zG{Yw-ci4pU(s>in7ytmqj&jmbJWVkSYGV7xsJ4)3aCgy7TjyFB^bfWMmiu*FP(tQ; z+PKm&o*IsQl%jy-y{9TQKwHvkc=cxIdMa!;Js-*}uhbe2WzZpyA8>YvaUd+>!5P89LdtE(*O-^o%8n;jVV?X%+K5nDDL))pSH;gKDNu?ul@U2Pusff6&nq6Pcs4i(kl^LvUU4)CRblUEphEbrMWrmu${4#4FSBc@=5{+O zh%8M`1JZed6FF)N0toSjYUcJ#j?!PcA=}$DH_0V(#(0`8Mc$j4>y(AP5}h9*K0VF$ zP^r(^^GruK;CzOtd>pEQyreG!y!Bu0vz3N=%$$`Uo9d))rRZ@z-8FV1$(ewsnfu## zJnyq_HzdLgQzi8j zV3JTRjqE!;eJvz?78}>}K5fmpkIgYC_b4Gs)-)$YNn1OVDyMTbi?02|j2fr_$3i79 z<^E-Ji{!H;+akW7bqfsml=hW{{g3{2s?aM(9C0H6y(T$sLyaJ6~N zo~z=o@@@G%8ZF5UXPzcX5tE_gt!7?nq8h5|s)jE;FoB^WB#IvY75%4pVP5`u4}nTQ zm$9|i-Iqq;_gQrPn+}@HT&ij+%Cd&MGL3{j>;`voAEqc$b`_U+TUH0ZA{O&6FUB4_ zyDo})KYGo&EUpnP+Mh%WN7`j=9R+zvSieo43zrF@*Osa!BQ`^nY+b@q(km#YLXfEK zcD8K)#+^cSJkW9W-{N~~R1F`@yX_YGT|~Sg;c9^f^wIem$^AoICR8C( z^S*L^pV8c_&!A?lnXeFIE}98nj?}(KtcL%2rLwfkER*610|!i6cANyCGpd=`1s9Vj z8|l*0U~Fe>deg2`a^g@dOGU%(ett7qz3bjiZ@R8|QKtpM9gve7n;K4B^@j#=6+Vy$ z^1GfyQ0R2>V{O7f3J)oepPdG19-o?{$vPnp+wE1+Jfylq=pMW|MgVw`pz;nVYW&QLCq{ zocJOo5?aIAN`a6U=~Kg%7?G&qU9Yb{+sMXQ#{=)QOE?M zxCH;cV>%R>{f^DCz0oB-FpJHF=va{3V&0&AK2uzFfyiXPMkbb^+M0hm-L^eo`>Cej z$$qAGY9!r;Uu9G16mQ8wZI&LD{zHSM{YU=0kzLGsvV+aNmyFf!SS#I|p*TjMQ}XvwM?528|37s!vwiIqJeSzHED+XrxNSx>Y3B1aCDbnj2ld}CU%dC1C25@+agz?9Io zs4SP7Q>g#ftbH7O*W<)C>c$JPEWgh|7XP4s(ir>b^I?OQWL?53J4>C(e~d|< zOqK8*U*%i(((=uexRlg3xnQ3o`}L!=CVmz@IT6OHK z@#zb3btv~~VI2|cU38IhS!~@TKoM$kS~e(&P0rsr$)10>ua(TMg~GQKK;~nph(GBF}V{q8cTW_ypvVDxu;! zxvIs?lt2yToI<^eT*de4ov_)FNO4 z>6Lor=4ZuU?})Y(y1hKso~AZLi+g+fkpP^}JG%I2{;o8#jW(MLX~5qiU945O{Seo6 zGwHI$rz~L9R#Bgay7r;at0c?G3fnlXdX^kL?k8%K+bfcfb3Egi2KUnC9$u7-4`J5r zcL;KTp%lLH@a~rFS$;Bs;E(p3&~!;F+4RT7rL{aN0c)E0SUJ6WuH%lpUGutpaMCN> zf2PBOSChMM;qNT-=E~F0@qXOY?I!BD#H@@zm6*AI$ax^0GL7vw0_Oxo4w9Q6bA84>Ae zDw?HZM3w|%Q@~#~U#VJ5bHnY^AdmvC{{Hd&MzhyKgdA;w^4fRj|iIO2y|(rHnd`;@Q;+tEI8?K0^%z?E!NIfvy~crew);hdS!iUQSlcb9}W} zZ1~y$*WHmw6xrU0XKlV(rg>2J`*(Q6o8)L}?Kg<-Ro5qCtWi&Kz24k%*y0T1B?3pT z+*06Tz#9hUzCdFd>pTVZRr^~IsL}t#F8365!0!f{`MsO@7+9DGh|%&t2+-| zpFT?J$e)$Us6~0Xlt6E9=?Srt?~WSbGpx(*IdPoW=Tx|~(f)iX1OVzin^}6as zfgNDTKbMQsrC+H5)y<;o$kYxvV$Ozw>;QtxV0#q%pkF;9DM2}`9)E(ca855zA_Dxl zK$!}!>xUoRCj%*7hzW)_T6Jdwgpv3z4hCx#OCCLBiZb(Y0<$dl2A#v9^N`^^Fa_-R zgIW&ZLpjRKI7`}NSA)?_$v5nkEBPHw+hdbgI+SeCZJum|1gZ&_tCWfM%wO-QUlzVn zPT}1gFNC5Z?J%oz4+b{Q20$p0#p=QJI&o5aVk&xKocKEVtr$FXAHVotBS(FK9#l*? zY<%$QjA>A^JNv!k!FVORligxi@B97KhvpJYbmv3796H474j#`+W)Tx(IC8f>lu#i(8AfM#*maOV3 zxmB7$d_0@E0kKw0VL5l9mw0sDSF<|SCLkmJ0s`nh?U`!Z_Kxl6jpDaGT#K<}dO2I% zt%ToW>7}jWTf2Q9el$C(8zNR4B6_!dN?L;T3}n)%7sFv$Y_4&+J5`0p(0Y+@=b_o{K~)}y;AnUD3V;L#x0jc(4-5y*c4q=EEXVRffsgq z3cs!jHoa##jBq4c7We%O-|H&lzxlhyQ%6PhsTwu#XIE*uedqg7xxi3iD^Itp%I@!q z32|!r9*0`#Hp$%`YxfvH%1rfxjvB;=n9H$TJr-@L*3^IjEi{b1>ug1}oOJHChAWrf z)EcD>rA})ZWy&IWWzY&_dRDrrnHf^LRRYqc_N*VB0=c@wu!^>-y2Bj+Ipwrl**@51&wcaujvR(m#Yv8hiQF=_YNO!Zj~&{ zf$M9wXs4$iE#c8|Zw}{MALHj8EPIBY8vdJL5}4cRENlrH?4@OW#tqZ{6O9%?H|szE z_`xEhA<(RF%$0X4Rg8$5jv_`tu|C|au8`0yBF54PB)oXM*6ZYojW6D0e>qX|y5!O7 zL^yixWKqkEqDOC)l1?O^7Zo$Hv=flaNBf`xR@!}%=we>mS6zpxrnuVOHOtM{u`D>3 zff^>d%u(hTK24}FZ7RO}da7L9+4DHKr-EfZ8XxwpbYZ2m*bI%DprNc|G#i3=opu)r zBdw28ll_cY@DoCMo-2QdvSm`X$6G>TcbOdp?R|sWg&>yVbs&BE4P~g&R_WvhBAm-& zSqSGn0PuqlKGb0$K2eU-ACVKCK_{e+jRrC@;PkcQ(ZK-L+;PNg`XEPTC=vhxZzmVB97Wj5AD zhlI}7-4UtRB0t+Zvn1QB7GUq0I;zw;aC;ivV}7$935D$7Iv`wg&ZgCUHV;u%@k9}p2oVCLPFs0!ovsEj1@HO zf&&j{2h)#+2Tq+1r@ZP3d5oY*18R7GCFrsAGco2r=E-c!lkoXA1(Wet?0*Q@V*08< zz0Yqk+)w|w!_@vmS|oeU>2UJ&w{yp$%Jl#VwTL?Q<(HFx|8@iFRmFx$dFg25+})pp z{s9v>ee~4ltx|bCBSz^i|1!tIe>XV{K5q)VFaBIF`8%)$mC*b2-X2ZQ)pXB0+xwl2iiXzo&<{e4Ey{&GAB58mpwJns z$0yRILb;044Xdy@WryvBaK5W!nc=?^@1S6{?_xCWaSR?VRhvx(LrE+cW6954ojIyT2IEbR1A)(W$f>Yx)ZP#1UP8JewxtTz~wi zFA^Bv#dnd-{*_=;;haGdz4sqKmH<<6l*1hxNSH{; z2M@1GP|o57`0s2SuLx9R0V@pEy?~3-|M>!-;l_yf(nV$Qgw8`=yf}Yh^ z30Nx?s~E_1!N{Db-8!hqxOLp>{_kRUK8&={57HC(>et5hfcG)Er9jS$o4lA}NCu_! zoxsrMS-OnZKSjn(*g9dk&2PkA#jl?WtV7bZ59S-Zdf(kap$!Gh$Atn+w4X)9p{_t| ztNlXe!i-;T3dG}S&wHS*>dS(@?DfHV7QIhDT9E`mpV8<-mOKxdef$}s zeTRZy5)vtKd(uYOPkT9Ug^377qO82o9*n5PC;1swq_h3Glx(=O@<`U!ix|gqPz8Br z@aFla^}062P%W%j2EB^#dc1h+ibK-u^%wiSSoInmy8B7ay&Bb0Ty2(t5_M4cE&StE z$XOg3>d4`AF+Z26l>VDm_nn60mh$7JQyHNAYzm~yw072JqZUT_H1sq;^^^b`gu-cA z-bVF|o5Qb(*l}GSeXe#M9>0R#bq~HEyMP~^c1Io;mS?R%DZ{RqR`T0@DRYODe}p@w znYj0lhQ&^H`LepTZlvHT{^kac%jpd}ln$#o$MK7BwNrYXqq{;doU2B(7rD2bkE0Kz zdSh{^`YNCqF4607JF~>7mIwZsT3pp|P~N!agZsz(W|r_(FU2`_HlWDE?kSCmgJK%2 znBzsA;l28=)J?jUoU+cGJT*sl6Bur;4aj86YnxP6dPsJ_He%nDGtfF^8;7W*+}HGn zVR~#<+J+Lf>*^$>mWoMzQyTv; z=iz%Q{C=VXR;2ZPE;BK#xNEI& zsqXf7?UyfnoVkM5Jh`yv0Sjv5(KOvACX4O1JglK~nDZRwY7&{e+*y0*Z6PAARQBBT zi>ZD(8;jYnjP{wm80q!sQ$eB3u-@r6=G&QY>)C|MfawxlAdSS?P&$Mb919{)-1;zg zWwQ4t8mh*gN^?DIshb*!*dCpWJHK8cF=&WSTPvzPx806lgBQ)+=#t5tm{tizxoi{!6{c55E7kB@~ zwvWePx3@YAU$$zc`6jfQnB9jDJ(ktn^NC-UztcagbT?S3Di;_MbX?PK_4;#l3vcQT zd=LvM1@n^V@wfyx?jIFC&bG&n>JtJL5vy}~yVb+atQxpB=jR$~W;&CXGC_>Aq|0}H z(vJg6*)(O>Cquf=uQ3(nWv*Hx=kX_6lliJFw!$8X|HH!8&Hb|L9?mMo6c{f;1(K?5 zgT4%zHB@<}_^>@abjF(6J835}(O%*x951?NZQ)?Q-vqcuQQ8SSp;WI zmilu>T5wf5Y7H4Vlod#t7c5Tih*d6#G#Y;z9VW|45>i!Y{Q8z|r)KyO;K#9(wmLTC z8#qwFEUT9MQZcIxH{V=>F6&G62^1o6$yip|-jX7V>fikarQ!nsv%D+_qD7OM*~45v z-Jy9{n3#q5=7h!9j2FyW|0RmLr;{qV7QU51aKtgau}x^Qa?9w8^qXij3-O`%#rDrN zA=9DZ88ogZo_VVlcK|6-x4(&tG3sk;^t#=;mjx%*Kel)BsDM~KMk?NF3pQBY?)DDR zmXP9O#c6vM-KkA7hFnkc|V|7sX3xy&lcbR^?by&(qhy^L$9D)5N-;sM6=XnP& z9sB-R-nO%44#s_nGyZQqKL5YC-~Wp&exY%Lv$R=etm{{}<&{pn21WmO(_<=4QFK5H z7~|LIzPS;{2v`g2PP0{L)|;XQ_x*6F`S4{7G8(^@S9Z+@85uu`jTRxL_Dws@(s$MW z7Dg+Zxr8ZF8n(3kK4eZ1^Ts^KmpzM=Pycc35&XU0Fl5Y?C`&E%*@4MAKvFi`ng-*n zmpjG(4e;)DQO(Yf-c#i|tuU_RV;Fg$e;5#;-Xr9Q@k^1OQ7S4%G6&<=ceVXV230DS zdi`tz`%2vwn#a47T;&j9bd2$e)n>hMG6O#a4QvwN_i$>dC~7P;QPtQx9PsDGH@}0N zj_HXwy;-kw*oi8Zxk9o)@5n3t!`50}nsQ-WNU>^Ri4FG$Y?5KUDQXCtaR@bcM9)tO zPWASt>IY;dzI`%2K9!fO+n+jZ)$@ngByVm6kqO_SeCji1d%5EPDKYtPWgE%8C zduc1F=>Pzrb^QKAh+#k@0svkEq(JXfT#^qKU3IXg??g^$C|MCG8Q*I}f5iCk6z@aT zYd5A+r2QvehpMH`s#Q)F7QwZ5s#?`z+BVgbUO97WRg*GSu$PncIy{+bPjQgwpY-9r z?7_s2eEs9?doOPv+-SL{z}qJM`!^h>LxOxJl0yzd$p)AEG?b4H{XSe~cOAihlK{Yp zpsgP|c>f6hUq*U=`2bW441NU|WaZ?n+MQmWnfV^DVXwKm__eREZ`4o_e$P?gD2n)} zyvykBK9Po|76AbPP*F@w%udSA$q6T~G3&`^zl@BGa}0Ho6h9(WS-HWGP&xK78)qAp zzvmJ93W0pd`bM#D5Eo=>X=!8>9h2f)4OkH~8ZU!qJ$MU|kdP#K!5SGz|CIM3?dTf| znyixP92+gKsCthK&;wnb!{;l3f6b99i@@Q%p^^x{f_KX4040`B|5oe%nYb@hK9cu) zNFI+JsvrMSfE}FwpGhq_1Y2SeUOHyp29=X(YLkWSI225taKnc=N)7wE*T)~zHQg0< ztL_|II~Dxw6Rn**^c}C;aup9tvNZ=L4$Z*m&j^K;ss1_yV98Na2J98@RBDF!4;v-< zYvUgSL=Y%0P;MBW|4I{_{DvuJ+SkA?`>(hQx7EUnAFk}&F^6ns%AOJMD_p^EE?C5N#)V8(oifnLBC<1tt#DDf9p0#YFi*I1PLc~y2vJemIZZ$B(aV1 z9*y`J26kb2>~ACBD zWBY+^$k)4v>|fwEZcZ8@-zJqW z&G^1A+s+RZa((e~A$YVrVHDnME@*f6)GvU5r#C6d?aERZ=zSH2XyKRRwZ{RPS?1WO zJ8ctMW^pP!&Y8a{bN@#E04|3UrKSFWtMV+{^rx!E0{hj!n{o+Lt(Q9=r`A^;53a(X zs8=vS?}csmTKP>Nn|x{(?bZ0S-wf~5d-3xYt?h1#u=O*W_9yi%Oc(eI%MP6{k;3O^ zQJ)SE&A1O{ocKDrY>Pn^yk`9w|917;>(9_Y0FQj9R7kh{nMwRhNK`@{Ryi_#NSHil z>cRFP>rzxx!ayZkg8ABTGEV{tRP}R6cBG^mY@a1ldSPqOj*6Vzd1?v$YkPR7UK6!z zGy*HZg``+cHp@laFj2xN;otx-x!ZflRJyGLdVu&X_lx_F1;DW3kl&+$~Kn zm5S*^9ZbNl^7b2SyDo-!wr2fPMi70ad~y>tjZ68&HN5^g-Y|5zL}ppTuInL6|B9)@ z67^35e74IDv3=-ITTGE}*Qs3{5_iYl0 zoi|0AoP!gG+W0mRq|SDqV~Bojki1h*3K%~@@5{j^oIX zj*dvv03|e{m3~38`hNmx;EA z_vG1ml|O%}pwfpQ+8EU{nG8?dL&7ow%a0s*(f=3~q>u+c2aJ&L;tChl_%SEF{=87X z#fpol!}zQ&1XbhYs!V$cwsA7pXN~gnIU6zSo(&?-X?bss(;~_51q}dDgK!V$1Wtm=Q@#r&_pXDgPUS~J*T=12a;ONN`9)I?Pqj=9bMw( zc!8-O`RoS6HOe`Md_IQIrQ>=eK8YoV*ImZeJFVlrwGJpp*~#-Ow>!cJ!J{a`OYW4y zd{o==*7A2TH_Kpu$gWwdS#Biw87uBOp113Xi@lq8Yf8vP9Yv#>oA70jryp8b$=%wt zGTxZYHts^Nqyq25{uNFcs`+XSlBmC?1(+;Yrnf#S$zi>%x4E^9kCU$VRs6})P=G@( z!DVZjK+}5jE?UX*lU9>ga(7zuGkmB7tJXFe+R09htSsyDRP*L1@fq@HHB%my!aBP5 zX1}}E2M=M`GL9#lol37exV?e_D_fPVRtAr(*D<_EMc8oAvXmp-ZQ(V(^-~|c&NkC3 zL}0;#Ac51iS(u@vRd)&R$-5JcO21{Cu_rh{;Ch46b?YS-BQL#}^ISEyZ(kiee^q(e zK5dW3s(+2jy^Kfi!wioQ`_UFf38j$-)t+ zb*}W2IL|_~k)#WiO%@>*0-0%99PrW9xx%$7nbUgJLl1*Lvn6GCcC`OqttxPrDzhm{JWBVK~lO{N8}6gX*lmsM=8|gif_UmEJcfApUe8qA$|=KFZrs?!!qL>NOfG znnNx8rnC5T|G`G|sw#YlhaWgRsd_os)(d~gGI&6+nG1_@H{~7D-)&)8?Y^f`xO*CQ zs6Af`;R;aHLDfDRx{|q^v}NonorAU0U%SE|$f0U0;MveV^u!gfPyGY4&&#Ht8wgzX zi#Bef#-E(-5sb=st7C9n9pVV%s<=5$M#r@9bQtZdoEM6jbHKdG$D&OZ88_VLh{|I+ zy{Q)=4DKHaJX~me*+qn@(@HEpem3H^{hstKQ2r2>d?9!N|2-|!1Xo+ZAuM|ChY%?1D3>r1ay^a|J*=MeX^X!QdeWyPgwb`z79 zk+!~H2ku@im22Bc)MBP3jANJ)fx0a_FP~-3=(HRVmX^ePHiuts)+Zf{bWhI-lO&c{ z@VmgyT!F3U8a}TYP19s#Jv2cqW?)32(rH!g9Y;>L8nXs;ooqJ87}@uI4mo*6O08q5k7#5T z5$Pp7P%_5TMR#XaiM(}y>S0QbzWxC=C-K-@O36@FNB^D?a4I6aDYG z?qi)5BPIQ$EV_F`om%8IgS)amiR4TZ4 z?J7q+Gf891IEEW;yd<4NBPCR$!*C0eh!-h~(iNVBBKx)jrQ);q*WZx2=2lI?fi zKr>0*0H;mD=j)Ut(h@&h^G0c7WOhzEW@szAC(HVq&B23hfx>MNe70HySNF2oe;ER zc5ZVuOumZ~aV)N-D62UcMhSF9{x_E!%{cljqBcu#S$`=3j;|Twa(1VVH%4-Y6%Xd zFxYEqJjj(K&vFzFqHv?>16*)xYAhRO7Yxw|X|r=;Kq2LU1i;b3MGFy!o2&KR$NSgZ z=^VZ4ZrXNMI4Nv*TUj%PY5FaQYiy#QM>ei@e5!5YO_K(Uu)4+c6?2_y9j)Lg~bH~M#MlT zc5L1kqT4W|>yxgUnx5GTOpiGoZ(ID%n&YOC{Pl*MA-tfxktddHM&si0>qpaKzmd)v z4;J^-M)=vn@pQH~H-qF%Gis{$t{XRyjUw-M*7>wRYuCRv%0N`_?WEH%O|dN0ZZ}%p zr1rja5l4qK0iZR(;rY7Se{k~jB z=O}Y;r;GzOtyE?5N=$t!Cdwo}G(-B^E*AorKsFwqdZ-mvkZ>BDkmatztf%R+sf z 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≶$7lC8 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/dev/null; then + echo "ERROR: Time $time is not a number; EXITING;"; + exit 1; + fi + if [ $time -gt 23 ] || [ $time -lt 0 ] ; then + echo "ERROR: Time should be in range ( 0-23 ); EXITING;"; + exit 1; + fi + + echo "Do you have public domain & valid SSL? (Y/n) " + echo "Y: if you have public domain & valid ssl certificate" + echo "n: If you don't have a public domain and a valid SSL certificate. Note: It is recommended to use this option only in development environments." + read -p "" flag + + if [ -z "$flag" ]; then + echo "'flag' was provided; EXITING;" + exit 1; + fi + ENABLE_INSECURE='' + if [ "$flag" = "n" ]; then + ENABLE_INSECURE='--set enable_insecure=true'; + fi + + read -p "Please provide the retention days to remove old reports ( Default: 3 )" reportExpirationInDays + + if [[ -z $reportExpirationInDays ]]; then + reportExpirationInDays=3 + fi + if ! [[ $reportExpirationInDays =~ ^[0-9]+$ ]]; then + echo "The variable \"reportExpirationInDays\" should contain only number; EXITING"; + exit 1; + fi + + read -p "Please provide slack webhook URL to notify server end issues on your slack channel : " slackWebhookUrl + + if [ -z $slackWebhookUrl ]; then + echo "slack webhook URL not provided; EXITING;" + exit 1; + fi + + valid_inputs=("yes" "no") + eSignetDeployed="" + + while [[ ! " ${valid_inputs[@]} " =~ " ${eSignetDeployed} " ]]; do + read -p "Is the eSignet service deployed? (yes/no): " eSignetDeployed + eSignetDeployed=${eSignetDeployed,,} # Convert input to lowercase + done + + if [[ $eSignetDeployed == "yes" ]]; then + echo "eSignet service is deployed. Proceeding with installation..." + else + echo "eSignet service is not deployed. hence will be skipping esignet related test-cases..." + fi + + echo Installing apitestrig + helm -n $NS install apitestrig mosip/apitestrig \ + --set crontime="0 $time * * *" \ + -f values.yaml \ + --version $CHART_VERSION \ + --set apitestrig.configmaps.s3.s3-host='http://minio.minio:9000' \ + --set apitestrig.configmaps.s3.s3-user-key='admin' \ + --set apitestrig.configmaps.s3.s3-region='' \ + --set apitestrig.configmaps.db.db-server="$DB_HOST" \ + --set apitestrig.configmaps.db.db-su-user="postgres" \ + --set apitestrig.configmaps.db.db-port="5432" \ + --set apitestrig.configmaps.apitestrig.ENV_USER="$ENV_USER" \ + --set apitestrig.configmaps.apitestrig.ENV_ENDPOINT="https://$API_INTERNAL_HOST" \ + --set apitestrig.configmaps.apitestrig.ENV_TESTLEVEL="smokeAndRegression" \ + --set apitestrig.configmaps.apitestrig.reportExpirationInDays="$reportExpirationInDays" \ + --set apitestrig.configmaps.apitestrig.slack-webhook-url="$slackWebhookUrl" \ + --set apitestrig.configmaps.apitestrig.eSignetDeployed="$eSignetDeployed" \ + --set apitestrig.configmaps.apitestrig.NS="$NS" \ + $ENABLE_INSECURE + + echo Installed apitestrig. + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +installing_apitestrig # calling function \ No newline at end of file diff --git a/deploy/apitestrig/values.yaml b/deploy/apitestrig/values.yaml new file mode 100644 index 0000000000..dc15566f41 --- /dev/null +++ b/deploy/apitestrig/values.yaml @@ -0,0 +1,17 @@ +modules: + prereg: + enabled: true + masterdata: + enabled: true + idrepo: + enabled: true + partner: + enabled: true + resident: + enabled: true + auth: + enabled: true + esignet: + enabled: true + mimoto: + enabled: true \ No newline at end of file diff --git a/deploy/uitestrig/README.md b/deploy/uitestrig/README.md new file mode 100644 index 0000000000..00b2177faf --- /dev/null +++ b/deploy/uitestrig/README.md @@ -0,0 +1,32 @@ +# UITESTRIG + +## Introduction +UITESTRIG will test end-to-end functional flows involving multiple UI modules. + +## Install +* Install +```sh +./install.sh +``` + +## Uninstall +* To uninstall UITESTRIG, run `delete.sh` script. +```sh +./delete.sh +``` + +## Run UITESTRIG manually + +#### CLI +* Download Kubernetes cluster `kubeconfig` file from `rancher dashboard` to your local. +* Install `kubectl` package to your local machine. +* Run UITESTRIG manually via CLI by creating a new job from an existing k8s cronjob. + ``` + kubectl --kubeconfig= -n UITESTRIG create job --from=cronjob/ + ``` + example: + ``` + kubectl --kubeconfig=/home/xxx/Downloads/qa4.config -n UITESTRIG create job --from=cronjob/cronjob-uitestrig cronjob-uitestrig + ``` + + diff --git a/deploy/uitestrig/copy_cm.sh b/deploy/uitestrig/copy_cm.sh new file mode 100644 index 0000000000..828bb6d6de --- /dev/null +++ b/deploy/uitestrig/copy_cm.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copy configmaps from other namespaces +# DST_NS: Destination namespace + +function copying_cm() { + UTIL_URL=https://raw.githubusercontent.com/mosip/mosip-infra/master/deployment/v3/utils/copy_cm_func.sh + COPY_UTIL=./copy_cm_func.sh + DST_NS=uitestrig + + wget -q $UTIL_URL -O copy_cm_func.sh && chmod +x copy_cm_func.sh + + $COPY_UTIL configmap global default $DST_NS + $COPY_UTIL configmap keycloak-host keycloak $DST_NS + $COPY_UTIL configmap artifactory-share artifactory $DST_NS + $COPY_UTIL configmap config-server-share config-server $DST_NS + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_cm # calling function \ No newline at end of file diff --git a/deploy/uitestrig/copy_secrets.sh b/deploy/uitestrig/copy_secrets.sh new file mode 100644 index 0000000000..47561e6ade --- /dev/null +++ b/deploy/uitestrig/copy_secrets.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copy secrets from other namespaces +# DST_NS: Destination namespace + +function copying_secrets() { + UTIL_URL=https://raw.githubusercontent.com/mosip/mosip-infra/master/deployment/v3/utils/copy_cm_func.sh + COPY_UTIL=./copy_cm_func.sh + DST_NS=uitestrig + + wget -q $UTIL_URL -O copy_cm_func.sh && chmod +x copy_cm_func.sh + + $COPY_UTIL secret keycloak-client-secrets keycloak $DST_NS + $COPY_UTIL secret s3 s3 $DST_NS + $COPY_UTIL secret postgres-postgresql postgres $DST_NS + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_secrets # calling function \ No newline at end of file diff --git a/deploy/uitestrig/delete.sh b/deploy/uitestrig/delete.sh new file mode 100644 index 0000000000..fac7ba3b56 --- /dev/null +++ b/deploy/uitestrig/delete.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Uninstalls uitestrig +## Usage: ./delete.sh [kubeconfig] + +if [ $# -ge 1 ] ; then + export KUBECONFIG=$1 +fi + +function deleting_uitestrig() { + NS=uitestrig + while true; do + read -p "Are you sure you want to delete uitestrig helm charts?(Y/n) " yn + if [ $yn = "Y" ] + then + helm -n $NS delete uitestrig + break + else + break + fi + done + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +deleting_uitestrig # calling function \ No newline at end of file diff --git a/deploy/uitestrig/install.sh b/deploy/uitestrig/install.sh new file mode 100644 index 0000000000..d1f48939ed --- /dev/null +++ b/deploy/uitestrig/install.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Installs uitestrig automation +## Usage: ./install.sh [kubeconfig] + +if [ $# -ge 1 ] ; then + export KUBECONFIG=$1 +fi + +NS=uitestrig +CHART_VERSION=0.0.1-develop + +echo Create $NS namespace +kubectl create ns $NS + + +function installing_uitestrig() { + ENV_NAME=$( kubectl -n default get cm global -o json |jq -r '.data."installation-domain"') + + read -p "Please enter the time(hr) to run the cronjob every day (time: 0-23) : " time + if [ -z "$time" ]; then + echo "ERROR: Time cannot be empty; EXITING;"; + exit 1; + fi + if ! [ $time -eq $time ] 2>/dev/null; then + echo "ERROR: Time $time is not a number; EXITING;"; + exit 1; + fi + if [ $time -gt 23 ] || [ $time -lt 0 ] ; then + echo "ERROR: Time should be in range ( 0-23 ); EXITING;"; + exit 1; + fi + + echo "Do you have public domain & valid SSL? (Y/n) " + echo "Y: if you have public domain & valid ssl certificate" + echo "n: if you don't have public domain & valid ssl certificate" + read -p "" flag + + if [ -z "$flag" ]; then + echo "'flag' was provided; EXITING;" + exit 1; + fi + ENABLE_INSECURE='' + if [ "$flag" = "n" ]; then + ENABLE_INSECURE='--set uitestrig.configmaps.uitestrig.ENABLE_INSECURE=true'; + fi + + echo Istio label + kubectl label ns $NS istio-injection=disabled --overwrite + helm repo update + + echo Copy configmaps + ./copy_cm.sh + + echo Copy secrets + ./copy_secrets.sh + + DB_HOST=$( kubectl -n default get cm global -o json |jq -r '.data."mosip-api-internal-host"' ) + PMP_HOST=$(kubectl -n default get cm global -o json |jq -r '.data."mosip-pmp-host"') + ADMIN_HOST=$(kubectl -n default get cm global -o json |jq -r '.data."mosip-admin-host"') + RESIDENT_HOST=$(kubectl -n default get cm global -o json |jq -r '.data."mosip-resident-host"') + API_INTERNAL_HOST=$( kubectl -n default get cm global -o json |jq -r '.data."mosip-api-internal-host"' ) + + echo Installing uitestrig + helm -n $NS install uitestrig mosip/uitestrig \ + --set crontime="0 $time * * *" \ + --version $CHART_VERSION \ + --set uitestrig.configmaps.s3.s3-host='http://minio.minio:9000' \ + --set uitestrig.configmaps.s3.s3-user-key='admin' \ + --set uitestrig.configmaps.s3.s3-region='' \ + --set uitestrig.configmaps.db.db-server="$DB_HOST" \ + --set uitestrig.configmaps.db.db-su-user="postgres" \ + --set uitestrig.configmaps.db.db-port="5432" \ + --set uitestrig.configmaps.uitestrig.apiInternalEndPoint="https://$API_INTERNAL_HOST" \ + --set uitestrig.configmaps.uitestrig.apiEnvUser="$API_INTERNAL_HOST" \ + --set uitestrig.configmaps.uitestrig.PmpPortalPath="https://$PMP_HOST" \ + --set uitestrig.configmaps.uitestrig.adminPortalPath="https://$ADMIN_HOST" \ + --set uitestrig.configmaps.uitestrig.residentPortalPath="https://$RESIDENT_HOST" \ + --set uitestrig.configmaps.uitestrig.NS="$NS" \ + $ENABLE_INSECURE + + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +installing_uitestrig # calling function \ No newline at end of file diff --git a/helm/apitestrig/.gitignore b/helm/apitestrig/.gitignore new file mode 100644 index 0000000000..ee3892e879 --- /dev/null +++ b/helm/apitestrig/.gitignore @@ -0,0 +1 @@ +charts/ diff --git a/helm/apitestrig/.helmignore b/helm/apitestrig/.helmignore new file mode 100644 index 0000000000..f0c1319444 --- /dev/null +++ b/helm/apitestrig/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/apitestrig/Chart.yaml b/helm/apitestrig/Chart.yaml new file mode 100644 index 0000000000..ffa48e31d3 --- /dev/null +++ b/helm/apitestrig/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: apitestrig +description: A Helm chart to deploy APITESTRIG for MOSIP modules +type: application +version: 0.0.1-develop +appVersion: "" +dependencies: + - name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 1.x.x +home: https://mosip.io +keywords: + - mosip + - apitestrig +maintainers: + - email: info@mosip.io + name: MOSIP diff --git a/helm/apitestrig/README.md b/helm/apitestrig/README.md new file mode 100644 index 0000000000..25c35e3591 --- /dev/null +++ b/helm/apitestrig/README.md @@ -0,0 +1,10 @@ +# APITESTRIG + +Helm chart to deploy APITESTRIG for `MOSIP` modules + +## TL;DR + +```console +$ helm repo add mosip https://mosip.github.io +$ helm install my-release mosip/apitestrig +``` diff --git a/helm/apitestrig/templates/NOTES.txt b/helm/apitestrig/templates/NOTES.txt new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/helm/apitestrig/templates/NOTES.txt @@ -0,0 +1 @@ + diff --git a/helm/apitestrig/templates/_helpers.tpl b/helm/apitestrig/templates/_helpers.tpl new file mode 100644 index 0000000000..d99caf0c43 --- /dev/null +++ b/helm/apitestrig/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* +Return the proper image name +*/}} +{{- define "apitestrig.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "apitestrig.volumePermissions.image" -}} +{{- include "common.images.image" ( dict "imageRoot" .Values.volumePermissions.image "global" .Values.global ) -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "apitestrig.imagePullSecrets" -}} +{{- include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.volumePermissions.image) "global" .Values.global) -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "apitestrig.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (printf "%s" (include "common.names.fullname" .)) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message. +*/}} +{{- define "apitestrig.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "apitestrig.validateValues.foo" .) -}} +{{- $messages := append $messages (include "apitestrig.validateValues.bar" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message -}} +{{- end -}} +{{- end -}} + +{{/* +Return podAnnotations +*/}} +{{- define "apitestrig.podAnnotations" -}} +{{- if .Values.podAnnotations }} +{{ include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) }} +{{- end }} +{{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} +{{ include "common.tplvalues.render" (dict "value" .Values.metrics.podAnnotations "context" $) }} +{{- end }} +{{- end -}} + +{{/* Create the name for restart cronjob */}} +{{- define "apitestrig.cronjob" -}} +{{ default (printf "cronjob-%s" (include "common.names.fullname" .)) .Values.serviceAccount.name }} +{{- end -}} \ No newline at end of file diff --git a/helm/apitestrig/templates/clusterrole.yaml b/helm/apitestrig/templates/clusterrole.yaml new file mode 100644 index 0000000000..da268fdf58 --- /dev/null +++ b/helm/apitestrig/templates/clusterrole.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "apitestrig.serviceAccountName" . }}-{{ .Release.Namespace }} + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get","patch","list","watch"] diff --git a/helm/apitestrig/templates/clusterrolebinding.yaml b/helm/apitestrig/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000..12594c8d18 --- /dev/null +++ b/helm/apitestrig/templates/clusterrolebinding.yaml @@ -0,0 +1,19 @@ +kind: ClusterRoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + name: {{ template "common.names.fullname" . }}-{{ .Release.Namespace }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "apitestrig.serviceAccountName" . }}-{{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ template "apitestrig.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} diff --git a/helm/apitestrig/templates/configmaps.yaml b/helm/apitestrig/templates/configmaps.yaml new file mode 100644 index 0000000000..4925083776 --- /dev/null +++ b/helm/apitestrig/templates/configmaps.yaml @@ -0,0 +1,21 @@ +{{- if .Values.apitestrig.configmaps }} +{{- range $cm_name, $cm_value := .Values.apitestrig.configmaps }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $cm_name }} + namespace: {{ $.Release.Namespace }} + labels: {{- include "common.labels.standard" $ | nindent 8 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 8 }} + {{- end }} +data: + {{- range $key, $value := $cm_value }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/apitestrig/templates/cronjob.yaml b/helm/apitestrig/templates/cronjob.yaml new file mode 100644 index 0000000000..cb3ce9a2cc --- /dev/null +++ b/helm/apitestrig/templates/cronjob.yaml @@ -0,0 +1,108 @@ +{{- range $modulename, $module := $.Values.modules }} +{{- if $module.enabled }} +--- +apiVersion: {{ include "common.capabilities.cronjob.apiVersion" $ }} +kind: CronJob +metadata: + name: {{ template "apitestrig.cronjob" $ }}-{{ $modulename }} + namespace: {{ $.Release.Namespace }} + annotations: + {{- if $.Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + +spec: + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 # remove jobs which are successfully executed + failedJobsHistoryLimit: 1 # except 1 recent failed job, remove jobs which are not successfully executed + #schedule: '*/3 * * * *' # cron spec of time, here, 8 o'clock + schedule: {{ $.Values.crontime }} + jobTemplate: + spec: + backoffLimit: 0 # this has very low chance of failing, as all this does + # is prompt kubernetes to schedule new replica set for + # the deployment + # activeDeadlineSeconds: 600 # timeout, makes most sense with + # "waiting for rollout" variant specified below + template: + spec: + # account configured above + restartPolicy: Never + serviceAccountName: {{ template "apitestrig.serviceAccountName" $ }} + initContainers: + {{- if $.Values.enable_insecure }} + {{- include "common.tplvalues.render" (dict "value" $.Values.initContainers "context" $) | nindent 12 }} + {{- end }} + containers: + - name: {{ template "apitestrig.serviceAccountName" $ }}-{{ $modulename }} + image: {{ $module.image.repository }}:{{ $module.image.tag }} + imagePullPolicy: {{ $module.image.pullPolicy }} + {{- if $.Values.lifecycleHooks }} + lifecycle: {{- include "common.tpvalues.render" (dict "value" $.Values.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- if $.Values.containerSecurityContext.enabled }} + securityContext: {{- omit $.Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if $.Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" $.Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if $.Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" $.Values.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: container_user + value: {{ $.Values.containerSecurityContext.runAsUser }} + - name: JDK_JAVA_OPTIONS + value: {{ $.Values.additionalResources.javaOpts }} + - name: MODULES + value: {{ $modulename }} + {{- if $.Values.extraEnvVars }} + {{- include "common.tpvalues.render" (dict "value" $.Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + envFrom: + {{- if $.Values.extraEnvVarsCM }} + {{- range $.Values.extraEnvVarsCM }} + - configMapRef: + name: {{ . }} + {{- end }} + {{- end }} + {{- if $.Values.extraEnvVarsSecret }} + {{- range $.Values.extraEnvVarsSecret }} + - secretRef: + name: {{ . }} + {{- end }} + {{- end }} + ports: + - name: spring-service + containerPort: {{ $.Values.springServicePort }} + volumeMounts: + {{- if $.Values.enable_insecure }} + - mountPath: /usr/local/openjdk-11/lib/security/cacerts + name: cacerts + subPath: cacerts + {{- end }} + {{- if $.Values.apitestrig.volumes }} + {{- range $volume_name, $volume_value := $.Values.apitestrig.volumes.configmaps }} + - name: {{ $volume_name }} + mountPath: {{ $volume_value.volumeMounts.mountPath }} + {{- end }} + {{- end }} + volumes: + {{- if $.Values.enable_insecure }} + - name: cacerts + emptyDir: {} + {{- end }} + {{- if $.Values.apitestrig.volumes }} + {{- range $volume_name, $volume_value := $.Values.apitestrig.volumes.configmaps }} + - name: {{ $volume_name }} + configMap: + defaultMode: {{ $volume_value.defaultMode }} + name: {{ $volume_name }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/apitestrig/templates/extra-list.yaml b/helm/apitestrig/templates/extra-list.yaml new file mode 100644 index 0000000000..9ac65f9e16 --- /dev/null +++ b/helm/apitestrig/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/helm/apitestrig/templates/secrets.yaml b/helm/apitestrig/templates/secrets.yaml new file mode 100644 index 0000000000..1ef8dc9893 --- /dev/null +++ b/helm/apitestrig/templates/secrets.yaml @@ -0,0 +1,22 @@ +{{- if .Values.apitestrig.secrets }} +{{- range $secret_name, $secret_value := .Values.apitestrig.secrets }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secret_name }} + namespace: {{ $.Release.Namespace }} + labels: {{- include "common.labels.standard" $ | nindent 8 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 8 }} + {{- end }} +type: Opaque +data: + {{- range $key, $value := $secret_value }} + {{ $key }}: {{ $value | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/apitestrig/templates/service-account.yaml b/helm/apitestrig/templates/service-account.yaml new file mode 100644 index 0000000000..466590df49 --- /dev/null +++ b/helm/apitestrig/templates/service-account.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + name: {{ template "apitestrig.serviceAccountName" . }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} diff --git a/helm/apitestrig/values.yaml b/helm/apitestrig/values.yaml new file mode 100644 index 0000000000..8b5beb6f8f --- /dev/null +++ b/helm/apitestrig/values.yaml @@ -0,0 +1,545 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass + +## Add labels to all the deployed resources +## +commonLabels: + app.kubernetes.io/component: mosip + +## Add annotations to all the deployed resources +## +commonAnnotations: {} + +## Kubernetes Cluster Domain +## +clusterDomain: cluster.local + +## Extra objects to deploy (value evaluated as a template) +## +extraDeploy: [] + +## Number of nodes +## +replicaCount: 1 + +service: + type: ClusterIP + port: 80 + ## loadBalancerIP for the SuiteCRM Service (optional, cloud specific) + ## ref: http://kubernetes.io/docs/user-guide/services/#type-loadbalancer + ## + ## loadBalancerIP: + ## + ## nodePorts: + ## http: + ## https: + ## + nodePorts: + http: "" + https: "" + ## Enable client source IP preservation + ## ref http://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + +## Port on which this particular spring service module is running. +springServicePort: 8083 + +## Configure extra options for liveness and readiness probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes +## + +## +# existingConfigmap: + +## Command and args for running the container (set to default if not set). Use array form +## +command: ['/bin/bash'] +args: ['-c', "/home/${container_user}/scripts/fetch_docker_image_hash_ids.sh"] + +## Deployment pod host aliases +## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +## +hostAliases: [] + +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: + cpu: 1500m + memory: 3500Mi + requests: + cpu: 1000m + memory: 3500Mi + +additionalResources: + ## Specify any JAVA_OPTS string here. These typically will be specified in conjunction with above resources + ## Example: java_opts: "-Xms500M -Xmx500M" + javaOpts: "-Xms2600M -Xmx2600M" + +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## Clamav container already runs as 'mosip' user, so we may not need to enable this +containerSecurityContext: + enabled: false + runAsUser: mosip + runAsNonRoot: true + +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## +podSecurityContext: + enabled: false + fsGroup: 1001 + +## Pod affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAffinityPreset: "" + +## Pod anti-affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAntiAffinityPreset: soft + +## Node affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## Allowed values: soft, hard +## +nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + ## + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + +## Affinity for pod assignment. Evaluated as a template. +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## +affinity: {} + +## Node labels for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Tolerations for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +## Pod extra labels +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} + +## Annotations for server pods. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} + +## pods' priority. +## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +# priorityClassName: "" + +## lifecycleHooks for the container to automate configuration before or after startup. +## +lifecycleHooks: {} + +## Custom Liveness probes for +## +customLivenessProbe: {} + +## Custom Rediness probes +## +customReadinessProbe: {} + +## Update strategy - only really applicable for deployments with RWO PVs attached +## If replicas = 1, an update can get "stuck", as the previous pod remains attached to the +## PV, and the "incoming" pod can never start. Changing the strategy to "Recreate" will +## terminate the single previous pod, so that the new, incoming pod can attach to the PV +## +updateStrategy: + type: RollingUpdate + +## Additional environment variables to set +## Example: +## extraEnvVars: +## - name: FOO +## value: "bar" +## +extraEnvVars: [] + +## ConfigMap with extra environment variables +## +extraEnvVarsCM: + - global + - s3 + - keycloak-host + - db + - apitestrig + - config-server-share + - artifactory-share +## Secret with extra environment variables +## +extraEnvVarsSecret: + - apitestrig + - s3 + - keycloak-client-secrets + - postgres-postgresql + +## Extra volumes to add to the deployment +## +extraVolumes: [] + +## Extra volume mounts to add to the container +## +extraVolumeMounts: [] + +## Add init containers to the pods. +## Example: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: + - command: + - /bin/bash + - -c + - if [ "$ENABLE_INSECURE" = "true" ]; then HOST=$( env | grep "mosip-api-internal-host" + |sed "s/mosip-api-internal-host=//g"); if [ -z "$HOST" ]; then echo "HOST + $HOST is empty; EXITING"; exit 1; fi; openssl s_client -servername "$HOST" + -connect "$HOST":443 > "$HOST.cer" 2>/dev/null & sleep 2 ; sed -i -ne '/-BEGIN + CERTIFICATE-/,/-END CERTIFICATE-/p' "$HOST.cer"; cat "$HOST.cer"; /usr/local/openjdk-11/bin/keytool + -delete -alias "$HOST" -keystore $JAVA_HOME/lib/security/cacerts -storepass + changeit; /usr/local/openjdk-11/bin/keytool -trustcacerts -keystore "$JAVA_HOME/lib/security/cacerts" + -storepass changeit -noprompt -importcert -alias "$HOST" -file "$HOST.cer" + ; if [ $? -gt 0 ]; then echo "Failed to add SSL certificate for host $host; + EXITING"; exit 1; fi; cp /usr/local/openjdk-11/lib/security/cacerts /cacerts; + fi + env: + - name: ENABLE_INSECURE + value: "true" + envFrom: + - configMapRef: + name: global + image: docker.io/openjdk:11-jre + imagePullPolicy: Always + name: cacerts + resources: {} + securityContext: + runAsUser: 0 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /cacerts + name: cacerts + +## Add sidecars to the pods. +## Example: +## sidecars: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +sidecars: {} + +persistence: + enabled: false + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack). + ## + # storageClass: "-" + ## + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + ## ReadWriteMany not supported by AWS gp2 + storageClass: + accessModes: + - ReadWriteOnce + size: 10M + existingClaim: + # Dir where config and keys are written inside container + mountDir: + +## Init containers parameters: +## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from the securityContext section. +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: "10" + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + pullSecrets: [] + ## - myRegistryKeySecretName + ## Init containers' resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + ## We usually recommend not to specify default resources and to leave this as a conscious + ## choice for the user. This also increases chances charts run on environments with little + ## resources, such as Minikube. If you do want to specify resources, uncomment the following + ## lines, adjust them as necessary, and remove the curly braces after 'resources:'. + ## + limits: {} + ## cpu: 100m + ## memory: 128Mi + ## + requests: {} + ## cpu: 100m + ## memory: 128Mi + ## + +## Specifies whether RBAC resources should be created +## +rbac: + create: true + +## Specifies whether a ServiceAccount should be created +## +serviceAccount: + create: true + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + ## + name: + +## Prometheus Metrics +## +metrics: + enabled: false + ## Prometheus pod annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: + prometheus.io/scrape: "true" + + endpointPath: + + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## + serviceMonitor: + ## If the operator is installed in your cluster, set to true to create a Service Monitor Entry + ## + enabled: true + ## Specify the namespace in which the serviceMonitor resource will be created + ## + # namespace: "" + ## Specify the interval at which metrics should be scraped + ## + interval: 10s + ## Specify the timeout after which the scrape is ended + ## + # scrapeTimeout: 30s + ## Specify Metric Relabellings to add to the scrape endpoint + ## + # relabellings: + ## Specify honorLabels parameter to add the scrape endpoint + ## + honorLabels: false + ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + ## + additionalLabels: {} + + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + enabled: false + additionalLabels: {} + namespace: '' + ## List of rules, used as template by Helm. + ## These are just examples rules inspired from https://awesome-prometheus-alerts.grep.to/rules.html + # rules: + # - alert: RabbitmqDown + # expr: rabbitmq_up{service="{{ template "rabbitmq.fullname" . }}"} == 0 + # for: 5m + # labels: + # severity: error + rules: [] + +## Admin swagger should have only internal access. Hence linked to internal gateway +istio: + enabled: false + gateways: + - istio-system/internal + prefix: + corsPolicy: + allowOrigins: + - prefix: https://api-internal.sandbox.xyz.net + allowCredentials: true + allowHeaders: + - Accept + - Accept-Encoding + - Accept-Language + - Connection + - Content-Type + - Cookie + - Host + - Referer + - Sec-Fetch-Dest + - Sec-Fetch-Mode + - Sec-Fetch-Site + - Sec-Fetch-User + - Origin + - Upgrade-Insecure-Requests + - User-Agent + - sec-ch-ua + - sec-ch-ua-mobile + - sec-ch-ua-platform + - x-xsrf-token + - xsrf-token + allowMethods: + - GET + - POST + - PATCH + - PUT + - DELETE + +modules: + prereg: + enabled: true + image: + repository: mosipqa/apitest-prereg + tag: develop + pullPolicy: Always + masterdata: + enabled: true + image: + repository: mosipqa/apitest-masterdata + tag: develop + pullPolicy: Always + idrepo: + enabled: true + image: + repository: mosipqa/apitest-idrepo + tag: develop + pullPolicy: Always + partner: + enabled: true + image: + repository: mosipqa/apitest-pms + tag: develop + pullPolicy: Always + pms: + enabled: true + image: + repository: mosipdev/apitest-pms + tag: develop + pullPolicy: Always + resident: + enabled: true + image: + repository: mosipqa/apitest-resident + tag: develop + pullPolicy: Always + auth: + enabled: true + image: + repository: mosipqa/apitest-auth + tag: develop + pullPolicy: Always + esignet: + enabled: true + image: + repository: mosipqa/apitest-esignet + tag: develop + pullPolicy: Always + mimoto: + enabled: true + image: + repository: mosipqa/apitest-mimoto + tag: develop + pullPolicy: Always + +crontime: "0 3 * * *" ## run cronjob every day at 3 AM (time hr: 0-23 ) + +apitestrig: + configmaps: + s3: + s3-host: 'http://minio.minio:9000' + s3-user-key: 'admin' + s3-region: '' + db: + db-port: '5432' + db-su-user: 'postgres' + db-server: 'api-internal.sandbox.xyz.net' + apitestrig: + ENV_USER: 'api-internal.sandbox' + ENV_ENDPOINT: 'https://api-internal.sandbox.xyz.net' + ENV_TESTLEVEL: 'smokeAndRegression' + authDemoServiceBaseURL: http://authdemo.authdemo + authDemoServicePort: 80 + eSignetDeployed: yes or no + push-reports-to-s3: 'yes' + authCertsPath: '/home/mosip/authcerts' + scripts: + fetch_docker_image_hash_ids.sh: | + #!/bin/bash + sleep 5 + export DOCKER_HASH_ID=$( kubectl get pod "$HOSTNAME" -n "$NS" -o jsonpath='{.status.containerStatuses[*].imageID}' | sed 's/ /\n/g' | grep -v 'istio' | sed 's/docker\-pullable\:\/\///g' ) + export DOCKER_IMAGE=$( kubectl get pod "$HOSTNAME" -n "$NS" -o jsonpath='{.status.containerStatuses[*].image}' | sed 's/ /\n/g' | grep -v 'istio' | sed 's/docker\-pullable\:\/\///g' ) + if [[ -z $DOCKER_HASH_ID ]]; then + echo "DOCKER_HASH_ID IS EMPTY;EXITING"; + exit 1; + fi + echo "DOCKER_HASH_ID ; $DOCKER_HASH_ID" + echo "DOCKER_IMAGE : $DOCKER_IMAGE" + kubectl get pods -A -o=jsonpath='{range .items[*]}{.metadata.namespace}{","}{.metadata.labels.app\.kubernetes\.io\/name}{","}{.status.containerStatuses[?(@.name!="istio-proxy")].image}{","}{.status.containerStatuses[?(@.name!="istio-proxy")].imageID}{","}{.metadata.creationTimestamp}{"\n"}' | sed 's/ /\n/g' | grep -vE 'istio*|longhorn*|cattle*|rancher|kube' | sed 's/docker\-pullable\:\/\///g' | sort -u | sed '/,,,/d' | awk -F ',' 'BEGIN {print "{ \"POD_NAME\": \"'$(echo $HOSTNAME)'\", \"DOCKER_IMAGE\": \"'$(echo $DOCKER_IMAGE)'\", \"DOCKER_HASH_ID\": \"'$(echo $DOCKER_HASH_ID)'\", \"k8s-cluster-image-list\": ["} {print "{"} {print "\"namespace\": \"" $1 "\","} {print "\"app_name\": \"" $2 "\","} {print "\"docker_image_name\": \"" $3 "\","} {print "\"docker_image_id\": \"" $4 "\","} {print "\"creation_timestamp\": \"" $5 "\"" } {print "},"} END {print "]}"}' | sed -z 's/},\n]/}\n]/g' | jq -r . | tee -a images-list.json + ## run entrypoint script + sleep 5 + cd /home/${container_user}/ + bash ./entrypoint.sh + secrets: + apitestrig: + volumes: + configmaps: + scripts: + defaultMode: 0777 + volumeMounts: + mountPath: '/home/mosip/scripts/' + +enable_insecure: false diff --git a/helm/uitestrig/.helmignore b/helm/uitestrig/.helmignore new file mode 100644 index 0000000000..f0c1319444 --- /dev/null +++ b/helm/uitestrig/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/uitestrig/Chart.yaml b/helm/uitestrig/Chart.yaml new file mode 100644 index 0000000000..8763cc302a --- /dev/null +++ b/helm/uitestrig/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +name: uitestrig +description: A Helm chart to deploy uitestrig to test working of MOSIP modules +type: application +version: 0.0.1-develop +appVersion: "" +dependencies: + - name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 1.x.x +home: https://mosip.io +keywords: + - mosip + - uitestrig + - testrig +maintainers: + - email: info@mosip.io + name: MOSIP diff --git a/helm/uitestrig/README.md b/helm/uitestrig/README.md new file mode 100644 index 0000000000..c313265108 --- /dev/null +++ b/helm/uitestrig/README.md @@ -0,0 +1,10 @@ +# UITESTRIG + +Helm chart to deploy UITESTRIG for `MOSIP` modules + +## TL;DR + +```console +$ helm repo add mosip https://mosip.github.io +$ helm install my-release mosip/uitestrig +``` diff --git a/helm/uitestrig/templates/NOTES.txt b/helm/uitestrig/templates/NOTES.txt new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/helm/uitestrig/templates/NOTES.txt @@ -0,0 +1 @@ + diff --git a/helm/uitestrig/templates/_helpers.tpl b/helm/uitestrig/templates/_helpers.tpl new file mode 100644 index 0000000000..4a344cd5ff --- /dev/null +++ b/helm/uitestrig/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* +Return the proper image name +*/}} +{{- define "uitestrig.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "uitestrig.volumePermissions.image" -}} +{{- include "common.images.image" ( dict "imageRoot" .Values.volumePermissions.image "global" .Values.global ) -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "uitestrig.imagePullSecrets" -}} +{{- include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.volumePermissions.image) "global" .Values.global) -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "uitestrig.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (printf "%s" (include "common.names.fullname" .)) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message. +*/}} +{{- define "uitestrig.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "uitestrig.validateValues.foo" .) -}} +{{- $messages := append $messages (include "uitestrig.validateValues.bar" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message -}} +{{- end -}} +{{- end -}} + +{{/* +Return podAnnotations +*/}} +{{- define "uitestrig.podAnnotations" -}} +{{- if .Values.podAnnotations }} +{{ include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) }} +{{- end }} +{{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} +{{ include "common.tplvalues.render" (dict "value" .Values.metrics.podAnnotations "context" $) }} +{{- end }} +{{- end -}} + +{{/* Create the name for restart cronjob */}} +{{- define "uitestrig.cronjob" -}} +{{ default (printf "cronjob-%s" (include "common.names.fullname" .)) .Values.serviceAccount.name }} +{{- end -}} diff --git a/helm/uitestrig/templates/clusterrole.yaml b/helm/uitestrig/templates/clusterrole.yaml new file mode 100644 index 0000000000..9ba5e13433 --- /dev/null +++ b/helm/uitestrig/templates/clusterrole.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "uitestrig.serviceAccountName" . }}-{{ .Release.Namespace }} + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get","patch","list","watch"] diff --git a/helm/uitestrig/templates/clusterrolebinding.yaml b/helm/uitestrig/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000..13f43a28ab --- /dev/null +++ b/helm/uitestrig/templates/clusterrolebinding.yaml @@ -0,0 +1,19 @@ +kind: ClusterRoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + name: {{ template "common.names.fullname" . }}-{{ .Release.Namespace }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "uitestrig.serviceAccountName" . }}-{{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ template "uitestrig.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} diff --git a/helm/uitestrig/templates/configmaps.yaml b/helm/uitestrig/templates/configmaps.yaml new file mode 100644 index 0000000000..30d26167fe --- /dev/null +++ b/helm/uitestrig/templates/configmaps.yaml @@ -0,0 +1,21 @@ +{{- if .Values.uitestrig.configmaps }} +{{- range $cm_name, $cm_value := .Values.uitestrig.configmaps }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $cm_name }} + namespace: {{ $.Release.Namespace }} + labels: {{- include "common.labels.standard" $ | nindent 8 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 8 }} + {{- end }} +data: + {{- range $key, $value := $cm_value }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/uitestrig/templates/cronjob.yaml b/helm/uitestrig/templates/cronjob.yaml new file mode 100644 index 0000000000..02bdf3a7e0 --- /dev/null +++ b/helm/uitestrig/templates/cronjob.yaml @@ -0,0 +1,106 @@ +{{- range $module := $.Values.modules }} +{{- if $module.enabled }} +--- +apiVersion: {{ include "common.capabilities.cronjob.apiVersion" $ }} +kind: CronJob +metadata: + name: {{ template "uitestrig.cronjob" $ }}-{{ $module.name }} + namespace: {{ $.Release.Namespace }} + annotations: + {{- if $.Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} +spec: + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 # remove jobs which are successfully executed + failedJobsHistoryLimit: 1 # except 1 recent failed job, remove jobs which are not successfully executed + #schedule: '*/3 * * * *' # cron spec of time + schedule: {{ $.Values.crontime }} + jobTemplate: + spec: + backoffLimit: 0 # this has very low chance of failing, as all this does + # is prompt kubernetes to schedule new replica set for + # the deployment + # activeDeadlineSeconds: 600 # timeout, makes most sense with + # "waiting for rollout" variant specified below + template: + spec: + # account configured above + serviceAccountName: {{ template "uitestrig.serviceAccountName" $ }} + restartPolicy: Never + containers: + - name: {{ $module.name }} + image: {{ $module.image.registry }}/{{ $module.image.repository }}:{{ $module.image.tag }} + imagePullPolicy: {{ $module.image.pullPolicy }} + {{- if $.Values.lifecycleHooks }} + lifecycle: {{- include "common.tpvalues.render" (dict "value" $.Values.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- if $.Values.containerSecurityContext.enabled }} + securityContext: {{- omit $.Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if $.Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" $.Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if $.Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" $.Values.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: container_user + value: "{{ $.Values.containerSecurityContext.runAsUser }}" + - name: JDK_JAVA_OPTIONS + value: "{{ $.Values.additionalResources.javaOpts }}" + - name: modules + value: "{{ $module.name }}" + {{- if $.Values.extraEnvVars }} + {{- include "common.tpvalues.render" (dict "value" $.Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + + envFrom: + {{- if $.Values.extraEnvVarsCM }} + {{- range $.Values.extraEnvVarsCM }} + - configMapRef: + name: {{ . }} + {{- end }} + {{- end }} + {{- if $.Values.extraEnvVarsSecret }} + {{- range $.Values.extraEnvVarsSecret }} + - secretRef: + name: {{ . }} + {{- end }} + {{- end }} + ports: + - name: spring-service + containerPort: {{ $.Values.springServicePort }} + volumeMounts: + {{- if $.Values.enable_insecure }} + - mountPath: /usr/local/openjdk-11/lib/security/cacerts + name: cacerts + subPath: cacerts + {{- end }} + {{- if $.Values.uitestrig.volumes }} + {{- range $volume_name, $volume_value := $.Values.uitestrig.volumes.configmaps }} + - name: {{ $volume_name }} + mountPath: {{ $volume_value.volumeMounts.mountPath }} + {{- end }} + {{- end }} + volumes: + {{- if $.Values.enable_insecure }} + - name: cacerts + emptyDir: {} + {{- end }} + {{- if $.Values.uitestrig.volumes }} + {{- range $volume_name, $volume_value := $.Values.uitestrig.volumes.configmaps }} + - name: {{ $volume_name }} + configMap: + defaultMode: {{ $volume_value.defaultMode }} + name: {{ $volume_name }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} + + diff --git a/helm/uitestrig/templates/extra-list.yaml b/helm/uitestrig/templates/extra-list.yaml new file mode 100644 index 0000000000..9ac65f9e16 --- /dev/null +++ b/helm/uitestrig/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/helm/uitestrig/templates/secrets.yaml b/helm/uitestrig/templates/secrets.yaml new file mode 100644 index 0000000000..8200c6701d --- /dev/null +++ b/helm/uitestrig/templates/secrets.yaml @@ -0,0 +1,21 @@ +{{- if .Values.uitestrig.secrets }} +{{- range $secret_name, $secret_value := .Values.uitestrig.secrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secret_name }} + namespace: {{ $.Release.Namespace }} + labels: {{- include "common.labels.standard" $ | nindent 8 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 8 }} + {{- end }} +type: Opaque +data: + {{- range $key, $value := $secret_value }} + {{ $key }}: {{ $value | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/uitestrig/templates/service-account.yaml b/helm/uitestrig/templates/service-account.yaml new file mode 100644 index 0000000000..28bff8af43 --- /dev/null +++ b/helm/uitestrig/templates/service-account.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + name: {{ template "uitestrig.serviceAccountName" . }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} diff --git a/helm/uitestrig/values.yaml b/helm/uitestrig/values.yaml new file mode 100644 index 0000000000..75e39c80a1 --- /dev/null +++ b/helm/uitestrig/values.yaml @@ -0,0 +1,511 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass + +## Add labels to all the deployed resources +## +commonLabels: + app.kubernetes.io/component: mosip + +## Add annotations to all the deployed resources +## +commonAnnotations: {} + +## Kubernetes Cluster Domain +## +clusterDomain: cluster.local + +## Extra objects to deploy (value evaluated as a template) +## +extraDeploy: [] + +## Number of nodes +## +replicaCount: 1 + +service: + type: ClusterIP + port: 80 + ## loadBalancerIP for the SuiteCRM Service (optional, cloud specific) + ## ref: http://kubernetes.io/docs/user-guide/services/#type-loadbalancer + ## + ## loadBalancerIP: + ## + ## nodePorts: + ## http: + ## https: + ## + nodePorts: + http: "" + https: "" + ## Enable client source IP preservation + ## ref http://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + +## Port on which this particular spring service module is running. +springServicePort: 8083 + +## Configure extra options for liveness and readiness probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes +## + +## +# existingConfigmap: + +## Command and args for running the container (set to default if not set). Use array form +## +command: ['/bin/bash'] +args: ['-c', "/home/${container_user}/scripts/fetch_docker_image_hash_ids.sh"] + +## Deployment pod host aliases +## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +## +hostAliases: [] + +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: + cpu: 1000m + memory: 3500Mi + requests: + cpu: 1000m + memory: 3500Mi + +additionalResources: + ## Specify any JAVA_OPTS string here. These typically will be specified in conjunction with above resources + ## Example: java_opts: "-Xms500M -Xmx500M" + javaOpts: "-Xms3500M -Xmx3500M" + +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## Clamav container already runs as 'mosip' user, so we may not need to enable this +containerSecurityContext: + enabled: false + runAsUser: mosip + runAsNonRoot: true + +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## +podSecurityContext: + enabled: false + fsGroup: 1001 + +## Pod affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAffinityPreset: "" + +## Pod anti-affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAntiAffinityPreset: soft + +## Node affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## Allowed values: soft, hard +## +nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + ## + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + +## Affinity for pod assignment. Evaluated as a template. +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## +affinity: {} + +## Node labels for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Tolerations for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +## Pod extra labels +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} + +## Annotations for server pods. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} + +## pods' priority. +## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +# priorityClassName: "" + +## lifecycleHooks for the container to automate configuration before or after startup. +## +lifecycleHooks: {} + +## Custom Liveness probes for +## +customLivenessProbe: {} + +## Custom Rediness probes +## +customReadinessProbe: {} + +## Update strategy - only really applicable for deployments with RWO PVs attached +## If replicas = 1, an update can get "stuck", as the previous pod remains attached to the +## PV, and the "incoming" pod can never start. Changing the strategy to "Recreate" will +## terminate the single previous pod, so that the new, incoming pod can attach to the PV +## +updateStrategy: + type: RollingUpdate + +## Additional environment variables to set +## Example: +## extraEnvVars: +## - name: FOO +## value: "bar" +## +extraEnvVars: [] + +## ConfigMap with extra environment variables +## +extraEnvVarsCM: + - global + - s3 + - keycloak-host + - db + - uitestrig + - config-server-share + - artifactory-share +## Secret with extra environment variables +## +extraEnvVarsSecret: + - s3 + - keycloak-client-secrets + - postgres-postgresql + +## Extra volumes to add to the deployment +## +extraVolumes: [] + +## Extra volume mounts to add to the container +## +extraVolumeMounts: [] + +## Add init containers to the pods. +## Example: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: + - command: + - /bin/bash + - -c + - if [ "$ENABLE_INSECURE" = "true" ]; then HOST=$( env | grep "mosip-api-internal-host" + |sed "s/mosip-api-internal-host=//g"); if [ -z "$HOST" ]; then echo "HOST + $HOST is empty; EXITING"; exit 1; fi; openssl s_client -servername "$HOST" + -connect "$HOST":443 > "$HOST.cer" 2>/dev/null & sleep 2 ; sed -i -ne '/-BEGIN + CERTIFICATE-/,/-END CERTIFICATE-/p' "$HOST.cer"; cat "$HOST.cer"; /usr/local/openjdk-11/bin/keytool + -delete -alias "$HOST" -keystore $JAVA_HOME/lib/security/cacerts -storepass + changeit; /usr/local/openjdk-11/bin/keytool -trustcacerts -keystore "$JAVA_HOME/lib/security/cacerts" + -storepass changeit -noprompt -importcert -alias "$HOST" -file "$HOST.cer" + ; if [ $? -gt 0 ]; then echo "Failed to add SSL certificate for host $host; + EXITING"; exit 1; fi; cp /usr/local/openjdk-11/lib/security/cacerts /cacerts; + fi + env: + - name: ENABLE_INSECURE + value: "true" + envFrom: + - configMapRef: + name: global + - configMapRef: + name: uitestrig + image: docker.io/openjdk:11-jre + imagePullPolicy: Always + name: cacerts + resources: {} + securityContext: + runAsUser: 0 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /cacerts + name: cacerts + +## Add sidecars to the pods. +## Example: +## sidecars: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +sidecars: {} + +persistence: + enabled: true + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack). + ## + # storageClass: "-" + ## + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + ## ReadWriteMany not supported by AWS gp2 + storageClass: + accessModes: + - ReadWriteOnce + size: 100m + +## Init containers parameters: +## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from the securityContext section. +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: "10" + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + pullSecrets: [] + ## - myRegistryKeySecretName + ## Init containers' resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + ## We usually recommend not to specify default resources and to leave this as a conscious + ## choice for the user. This also increases chances charts run on environments with little + ## resources, such as Minikube. If you do want to specify resources, uncomment the following + ## lines, adjust them as necessary, and remove the curly braces after 'resources:'. + ## + limits: {} + ## cpu: 100m + ## memory: 128Mi + ## + requests: {} + ## cpu: 100m + ## memory: 128Mi + ## + +## Specifies whether RBAC resources should be created +## +rbac: + create: true + +## Specifies whether a ServiceAccount should be created +## +serviceAccount: + create: true + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + ## + name: + +## Prometheus Metrics +## +metrics: + enabled: false + ## Prometheus pod annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: + prometheus.io/scrape: "true" + + endpointPath: + + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## + serviceMonitor: + ## If the operator is installed in your cluster, set to true to create a Service Monitor Entry + ## + enabled: true + ## Specify the namespace in which the serviceMonitor resource will be created + ## + # namespace: "" + ## Specify the interval at which metrics should be scraped + ## + interval: 10s + ## Specify the timeout after which the scrape is ended + ## + # scrapeTimeout: 30s + ## Specify Metric Relabellings to add to the scrape endpoint + ## + # relabellings: + ## Specify honorLabels parameter to add the scrape endpoint + ## + honorLabels: false + ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + ## + additionalLabels: {} + + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + enabled: false + additionalLabels: {} + namespace: '' + ## List of rules, used as template by Helm. + ## These are just examples rules inspired from https://awesome-prometheus-alerts.grep.to/rules.html + # rules: + # - alert: RabbitmqDown + # expr: rabbitmq_up{service="{{ template "rabbitmq.fullname" . }}"} == 0 + # for: 5m + # labels: + # severity: error + rules: [] + +## Admin swagger should have only internal access. Hence linked to internal gateway +istio: + enabled: false + gateways: + - istio-system/internal + prefix: + corsPolicy: + allowOrigins: + - prefix: https://api-internal.sandbox.xyz.net + allowCredentials: true + allowHeaders: + - Accept + - Accept-Encoding + - Accept-Language + - Connection + - Content-Type + - Cookie + - Host + - Referer + - Sec-Fetch-Dest + - Sec-Fetch-Mode + - Sec-Fetch-Site + - Sec-Fetch-User + - Origin + - Upgrade-Insecure-Requests + - User-Agent + - sec-ch-ua + - sec-ch-ua-mobile + - sec-ch-ua-platform + - x-xsrf-token + - xsrf-token + allowMethods: + - GET + - POST + - PATCH + - PUT + - DELETE + +modules: + - name: admin-ui + enabled: true + image: + registry: docker.io + repository: mosipqa/uitest-admin + tag: develop + pullPolicy: Always + - name: pmp-ui + enabled: true + image: + registry: docker.io + repository: mosipqa/uitest-pmp + tag: develop + pullPolicy: Always + - name: resident-ui + enabled: true + image: + registry: docker.io + repository: mosipqa/uitest-resident + tag: develop + pullPolicy: Always + +crontime: "0 3 * * *" ## run cronjob every day at 3 AM (time hr: 0-23 ) + +uitestrig: + configmaps: + s3: + s3-host: 'http://minio.minio:9000' + s3-user-key: 'admin' + s3-region: '' + db: + db-port: '5432' + db-su-user: 'postgres' + db-server: 'api-internal.sandbox.xyz.net' + uitestrig: + apiInternalEndPoint: 'https://api-internal.sandbox.xyz.net' + apiEnvUser: 'api-internal.sandbox.xyz.net' + PmpPortalPath: 'https://pmp.sandbox.xyz.net' + adminPortalPath: 'https://admin.sandbox.xyz.net' + residentPortalPath: 'https://resident.sandbox.xyz.net' + CHROME_DRIVER_CPU_LIMIT: "2" + CHROME_DRIVER_MEMORY: 3g + loginlang: sin + push-reports-to-s3: 'yes' + s3-account: uitestrig + scripts: + fetch_docker_image_hash_ids.sh: | + #!/bin/bash + sleep 5 + export DOCKER_HASH_ID=$( kubectl get pod "$HOSTNAME" -n "$NS" -o jsonpath='{.status.containerStatuses[*].imageID}' | sed 's/ /\n/g' | grep -v 'istio' | sed 's/docker\-pullable\:\/\///g' ) + export DOCKER_IMAGE=$( kubectl get pod "$HOSTNAME" -n "$NS" -o jsonpath='{.status.containerStatuses[*].image}' | sed 's/ /\n/g' | grep -v 'istio' | sed 's/docker\-pullable\:\/\///g' ) + if [[ -z $DOCKER_HASH_ID ]]; then + echo "DOCKER_HASH_ID IS EMPTY;EXITING"; + exit 1; + fi + echo "DOCKER_HASH_ID ; $DOCKER_HASH_ID" + echo "DOCKER_IMAGE : $DOCKER_IMAGE" + kubectl get pods -A -o=jsonpath='{range .items[*]}{.metadata.namespace}{","}{.metadata.labels.app\.kubernetes\.io\/name}{","}{.status.containerStatuses[?(@.name!="istio-proxy")].image}{","}{.status.containerStatuses[?(@.name!="istio-proxy")].imageID}{","}{.metadata.creationTimestamp}{"\n"}' | sed 's/ /\n/g' | grep -vE 'istio*|longhorn*|cattle*|rancher|kube' | sed 's/docker\-pullable\:\/\///g' | sort -u | sed '/,,,/d' | awk -F ',' 'BEGIN {print "{ \"POD_NAME\": \"'$(echo $HOSTNAME)'\", \"DOCKER_IMAGE\": \"'$(echo $DOCKER_IMAGE)'\", \"DOCKER_HASH_ID\": \"'$(echo $DOCKER_HASH_ID)'\", \"k8s-cluster-image-list\": ["} {print "{"} {print "\"namespace\": \"" $1 "\","} {print "\"app_name\": \"" $2 "\","} {print "\"docker_image_name\": \"" $3 "\","} {print "\"docker_image_id\": \"" $4 "\","} {print "\"creation_timestamp\": \"" $5 "\"" } {print "},"} END {print "]}"}' | sed -z 's/},\n]/}\n]/g' | jq -r . | tee -a images-list.json + ## run entrypoint script + sleep 5 + cd /home/${container_user}/ + bash ./entrypoint.sh + secrets: + volumes: + configmaps: + scripts: + defaultMode: 0777 + volumeMounts: + mountPath: '/home/mosip/scripts/' + +enable_insecure: false From 0871f04bc1aca6ba04fb3fcc9d51336730827d66 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Thu, 12 Sep 2024 15:50:02 +0530 Subject: [PATCH 27/43] MOSIP-35671 -Merging java21 with develop Signed-off-by: Mohanachandran S --- .../java/io/mosip/testrig/apirig/dbaccess/AuditDBManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/dbaccess/AuditDBManager.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/dbaccess/AuditDBManager.java index 67fe0de32a..e859826ea6 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/dbaccess/AuditDBManager.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/dbaccess/AuditDBManager.java @@ -146,7 +146,6 @@ private static SessionFactory getDataBaseConnectionSessionFactory(String dbName) config.setProperty("hibernate.show_sql", propsKernel.getProperty("show_sql")); config.setProperty("hibernate.current_session_context_class", propsKernel.getProperty("current_session_context_class")); - config.addFile(new File(MosipTestRunner.getGlobalResourcePath() + "/dbFiles/dbConfig.xml")); factory = config.buildSessionFactory(); } catch (HibernateException e) { DBCONNECTION_LOGGER.error("Exception in Database Connection with following message: " + e.getMessage()); From bd984ff8bb0121c089ad48561bc54ee14010d013 Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Thu, 12 Sep 2024 16:21:00 +0530 Subject: [PATCH 28/43] MOSIP-35642 Signed-off-by: Nandhukumar --- .../main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index 55724ea6b1..505eaf8480 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -3116,6 +3116,8 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { } if (jsonString.contains("$1STLANG$")) jsonString = replaceKeywordWithValue(jsonString, "$1STLANG$", BaseTestCase.languageList.get(0)); + if (jsonString.contains("$2NDLANG$")) + jsonString = replaceKeywordWithValue(jsonString, "$2NDLANG$", BaseTestCase.languageList.get(1)); if (jsonString.contains(GlobalConstants.KEYCLOAK_USER_1)) From 940b4dc574729a841521548e904df8ca4aea9b3e Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 17 Sep 2024 13:51:28 +0530 Subject: [PATCH 29/43] MOSIP-35671 -Merging java21 with develop Signed-off-by: Mohanachandran S --- apitest-commons/pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index c164141095..7e5aeb7fa6 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -55,7 +55,7 @@ 1.11.368 - 6.5.2.Final + 5.2.17.Final 1.1.2-incubating @@ -471,6 +471,10 @@ org.bouncycastle bcpkix-jdk15on + + org.hibernate.orm + hibernate-core + From f3bc4664fdd52a2cae0ae323daf13af9b7d12093 Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Tue, 17 Sep 2024 15:33:21 +0530 Subject: [PATCH 30/43] MOSIP-35770 Signed-off-by: Nandhukumar --- .../apirig/report/EmailableReport.java | 142 ++++++++++++++---- .../apirig/testrunner/ExtractResource.java | 2 + .../testrig/apirig/utils/ConfigManager.java | 11 ++ .../testrig/apirig/utils/GlobalConstants.java | 3 + .../apirig/utils/SkipTestCaseHandler.java | 62 ++++++++ 5 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/SkipTestCaseHandler.java diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/report/EmailableReport.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/report/EmailableReport.java index c000ea1ee5..4ae90c3a07 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/report/EmailableReport.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/report/EmailableReport.java @@ -47,6 +47,9 @@ public class EmailableReport implements IReporter { protected PrintWriter writer; protected final List suiteResults = Lists.newArrayList(); + + protected final boolean reportIgnoredTestCases = ConfigManager.reportIgnoredTestCases(); + protected final boolean reportKnownIssueTestCases = ConfigManager.reportKnownIssueTestCases(); // Reusable buffer private final StringBuilder buffer = new StringBuilder(); @@ -58,6 +61,7 @@ public class EmailableReport implements IReporter { private int totalPassedTests = 0; private int totalSkippedTests = 0; private int totalIgnoredTests = 0; + private int totalKnownIssueTests = 0; private int totalFailedTests = 0; private long totalDuration = 0; @@ -89,20 +93,31 @@ public void generateReport(List xmlSuites, List suites, String writer.close(); int totalTestCases = 0; - if (ConfigManager.reportIgnoredTestCases()) { - totalTestCases = totalPassedTests + totalSkippedTests + totalFailedTests + totalIgnoredTests; - }else { - totalTestCases = totalPassedTests + totalSkippedTests + totalFailedTests; + + totalTestCases = totalPassedTests + totalSkippedTests + totalFailedTests; + + if (reportIgnoredTestCases) { + totalTestCases = totalTestCases + totalIgnoredTests; + } + if (reportKnownIssueTestCases) { + totalTestCases = totalTestCases + totalKnownIssueTests; } String oldString = System.getProperty(GlobalConstants.EMAILABLEREPORT2NAME); String temp = ""; - if (ConfigManager.reportIgnoredTestCases()) { + + temp = "-report_T-" + totalTestCases + "_P-" + totalPassedTests + "_S-" + totalSkippedTests + "_F-" + + totalFailedTests; + + if (reportIgnoredTestCases && reportKnownIssueTestCases) { + temp = "-report_T-" + totalTestCases + "_P-" + totalPassedTests + "_S-" + totalSkippedTests + "_F-" + + totalFailedTests + "_I-" + totalIgnoredTests + "_KI-" + totalKnownIssueTests; + } else if (reportIgnoredTestCases && !(reportKnownIssueTestCases)) { temp = "-report_T-" + totalTestCases + "_P-" + totalPassedTests + "_S-" + totalSkippedTests + "_F-" + totalFailedTests + "_I-" + totalIgnoredTests; - }else { + } else if (reportKnownIssueTestCases && !(reportIgnoredTestCases)) { temp = "-report_T-" + totalTestCases + "_P-" + totalPassedTests + "_S-" + totalSkippedTests + "_F-" - + totalFailedTests; + + totalFailedTests + "_KI-" + totalKnownIssueTests; } String newString = oldString.replace("-report", temp); @@ -194,6 +209,7 @@ protected void writeStylesheet() { writer.print(".num {text-align:center}"); writer.print(".orange-bg {background-color: #FFA500}"); writer.print(".grey-bg {background-color: #808080}"); + writer.print(".thich-orange-bg {background-color: #CC5500}"); writer.print(".green-bg {background-color: #0A0}"); writer.print(".attn {background-color: #D00}"); writer.print(".passedodd td {background-color: #3F3}"); @@ -204,6 +220,8 @@ protected void writeStylesheet() { writer.print(".failedeven td,.stripe {background-color: #D00}"); writer.print(".ignoredodd td {background-color: #808080}"); writer.print(".ignoredeven td {background-color: #808080}"); + writer.print(".known_issuesodd td {background-color: #CC5500}"); + writer.print(".known_issueseven td {background-color: #CC5500}"); writer.print(".stacktrace {white-space:pre;font-family:monospace}"); writer.print(".totop {font-size:85%;text-align:center;border-bottom:2px solid #000}"); writer.print(""); @@ -244,6 +262,7 @@ protected void writeSuiteSummary() { totalPassedTests = 0; totalSkippedTests = 0; totalIgnoredTests = 0; + totalKnownIssueTests = 0; totalFailedTests = 0; totalDuration = 0; @@ -251,7 +270,7 @@ protected void writeSuiteSummary() { int testIndex = 0; for (SuiteResult suiteResult : suiteResults) { - writer.print(""); + writer.print(""); writer.print(Utils.escapeHtml(suiteResult.getSuiteName() + " ---- " + "Report Date: " + formattedDate + " ---- " + "Tested Environment: " + System.getProperty("env.endpoint").replaceAll(".*?\\.([^\\.]+)\\..*", "$1") + " ---- " @@ -259,32 +278,32 @@ protected void writeSuiteSummary() { writer.print(GlobalConstants.TRTR); // Left column: "Tested Component Details" with central alignment - writer.print("
");
+			writer.print("
");
 			writer.print(Utils.escapeHtml("Server Component Details"));
 			writer.print("");
 
 			// Right column: Details from AdminTestUtil.getServerComponentsDetails() without bold formatting
-			writer.print("
");
+			writer.print("
");
 			writer.print(Utils.escapeHtml(AdminTestUtil.getServerComponentsDetails()));
 			writer.print("
"); writer.print(GlobalConstants.TRTR); // Left column: "Tested Component Details" with central alignment - writer.print("
");
+			writer.print("
");
 			writer.print(Utils.escapeHtml("End Points used"));
 			writer.print("");
 
 			// Right column: Details from AdminTestUtil.getServerComponentsDetails() without bold formatting
-			writer.print("
");
+			writer.print("
");
 			writer.print(Utils.escapeHtml(GlobalMethods.getComponentDetails()));
 			writer.print("
"); writer.print(GlobalConstants.TRTR); if (GlobalMethods.getServerErrors().equals("No server errors")) { - writer.print("
");
+				writer.print("
");
 			} else {
 				writer.print(
-						"
");
+						"
");
 			}
 			writer.print(Utils.escapeHtml("Server Errors " + "\n" + GlobalMethods.getServerErrors()));
 			writer.print("
"); @@ -297,9 +316,13 @@ protected void writeSuiteSummary() { writer.print("# Passed"); writer.print("# Skipped"); writer.print("# Failed"); - if (ConfigManager.reportIgnoredTestCases()) { + if (reportIgnoredTestCases) { writer.print("# Ignored"); } + + if (reportKnownIssueTestCases) { + writer.print("# Known_Issues"); + } writer.print("Execution Time (ms)"); // writer.print("Included Groups"); // writer.print("Excluded Groups"); @@ -308,13 +331,20 @@ protected void writeSuiteSummary() { for (TestResult testResult : suiteResult.getTestResults()) { int passedTests = testResult.getPassedTestCount(); int ignoredTests = testResult.getIgnoredTestCount(); + int knownIssueTests = testResult.getKnownIssueTestCount(); int skippedTests = testResult.getSkippedTestCount(); int failedTests = testResult.getFailedTestCount(); long duration = testResult.getDuration(); int totalTests = 0; - totalTests = ConfigManager.reportIgnoredTestCases() - ? (passedTests + skippedTests + failedTests + ignoredTests) - : (passedTests + skippedTests + failedTests); + + totalTests = passedTests + skippedTests + failedTests; + + if (reportIgnoredTestCases) { + totalTests = totalTests + ignoredTests; + } + if (reportKnownIssueTestCases) { + totalTests = totalTests + knownIssueTests; + } // All test cases are ignored. Hence don't print anything in the report. if (totalTests < 1) continue; @@ -339,9 +369,13 @@ protected void writeSuiteSummary() { writeTableData(integerFormat.format(skippedTests), (skippedTests > 0 ? "num orange-bg" : "num")); writeTableData(integerFormat.format(failedTests), (failedTests > 0 ? GlobalConstants.NUMATTN : "num")); // print the ignored column based on the flag - if (ConfigManager.reportIgnoredTestCases()) { + if (reportIgnoredTestCases) { writeTableData(integerFormat.format(ignoredTests), (ignoredTests > 0 ? "num grey-bg" : "num")); } + + if (reportKnownIssueTestCases) { + writeTableData(integerFormat.format(knownIssueTests), (knownIssueTests > 0 ? "num thich-orange-bg" : "num")); + } writeTableData(decimalFormat.format(duration), "num"); // writeTableData(testResult.getIncludedGroups()); // writeTableData(testResult.getExcludedGroups()); @@ -352,6 +386,7 @@ protected void writeSuiteSummary() { totalSkippedTests += skippedTests; totalFailedTests += failedTests; totalIgnoredTests += ignoredTests; + totalKnownIssueTests += knownIssueTests; totalDuration += duration; testIndex++; @@ -362,9 +397,16 @@ protected void writeSuiteSummary() { writer.print(""); writer.print("Total"); writeTableHeader("", "num"); - if (ConfigManager.reportIgnoredTestCases()) { - writeTableHeader(integerFormat - .format(totalPassedTests + totalSkippedTests + totalFailedTests + totalIgnoredTests), "num"); + + if (reportIgnoredTestCases && reportKnownIssueTestCases) { + writeTableHeader(integerFormat.format(totalPassedTests + totalSkippedTests + totalFailedTests + + totalIgnoredTests + totalKnownIssueTests), "num"); + } else if (reportIgnoredTestCases && !(reportKnownIssueTestCases)) { + writeTableHeader(integerFormat.format(totalPassedTests + totalSkippedTests + totalFailedTests + + totalIgnoredTests), "num"); + } else if (reportKnownIssueTestCases && !(reportIgnoredTestCases)) { + writeTableHeader(integerFormat.format(totalPassedTests + totalSkippedTests + totalFailedTests + +totalKnownIssueTests), "num"); } else { writeTableHeader(integerFormat.format(totalPassedTests + totalSkippedTests + totalFailedTests), "num"); } @@ -373,10 +415,14 @@ protected void writeSuiteSummary() { (totalSkippedTests > 0 ? "num orange-bg" : "num")); writeTableHeader(integerFormat.format(totalFailedTests), (totalFailedTests > 0 ? GlobalConstants.NUMATTN : "num")); - if (ConfigManager.reportIgnoredTestCases()) { + if (reportIgnoredTestCases) { writeTableHeader(integerFormat.format(totalIgnoredTests), (totalIgnoredTests > 0 ? "num grey-bg" : "num")); } + if (reportKnownIssueTestCases) { + writeTableHeader(integerFormat.format(totalKnownIssueTests), + (totalKnownIssueTests > 0 ? "num thich-orange-bg" : "num")); + } writeTableHeader(decimalFormat.format(totalDuration), "num"); writer.print(GlobalConstants.TR); } @@ -401,9 +447,17 @@ protected static Set getResultsSubSet(Set resultsSet, } else { // Skip the test result } + } else if (subSetString.contains(GlobalConstants.KNOWN_ISSUES_STRING)) { + if (containsAny(throwable.getMessage(), subSetString)) { + // Add only results which are skipped due to feature not supported + testResultsSubList.add(result); + } else { + // Skip the test result + } } else { // Service not deployed. Hence skipping the testcase // skipped if (!throwable.getMessage().contains(GlobalConstants.FEATURE_NOT_SUPPORTED) - && !throwable.getMessage().contains(GlobalConstants.SERVICE_NOT_DEPLOYED)) { + && !throwable.getMessage().contains(GlobalConstants.SERVICE_NOT_DEPLOYED) + && !throwable.getMessage().contains(GlobalConstants.KNOWN_ISSUES)) { // Add only results which are not skipped due to feature not supported testResultsSubList.add(result); } else { @@ -451,12 +505,19 @@ protected void writeScenarioSummary() { for (TestResult testResult : suiteResult.getTestResults()) { int passedTests = testResult.getPassedTestCount(); int ignoredTests = testResult.getIgnoredTestCount(); + int knownIssueTests = testResult.getKnownIssueTestCount(); int skippedTests = testResult.getSkippedTestCount(); int failedTests = testResult.getFailedTestCount(); int totalTests = 0; - totalTests = ConfigManager.reportIgnoredTestCases() - ? (passedTests + skippedTests + failedTests + ignoredTests) - : (passedTests + skippedTests + failedTests); + + totalTests = passedTests + skippedTests + failedTests; + + if (reportIgnoredTestCases) { + totalTests = totalTests + ignoredTests; + } + if (reportKnownIssueTestCases) { + totalTests = totalTests + knownIssueTests; + } // All test cases are ignored. Hence don't print anything in the report. if (totalTests < 1) continue; @@ -466,10 +527,16 @@ protected void writeScenarioSummary() { String testName = Utils.escapeHtml(testResult.getTestName()); - if (ConfigManager.reportIgnoredTestCases()) { + if (reportIgnoredTestCases) { scenarioIndex += writeScenarioSummary(testName + " — Ignored", testResult.getIgnoredTestResults(), "ignored", scenarioIndex); } + + if (reportKnownIssueTestCases) { + scenarioIndex += writeScenarioSummary(testName + " — known_issues", + testResult.getKnownIssueTestResults(), "known_issues", scenarioIndex); + } + scenarioIndex += writeScenarioSummary(testName + " — Failed", testResult.getFailedTestResults(), "failed", scenarioIndex); scenarioIndex += writeScenarioSummary(testName + " — Skipped", testResult.getSkippedTestResults(), @@ -582,9 +649,13 @@ protected void writeScenarioDetails() { writer.print(Utils.escapeHtml(testResult.getTestName())); writer.print(""); - if (ConfigManager.reportIgnoredTestCases()) { + if (reportIgnoredTestCases) { scenarioIndex += writeScenarioDetails(testResult.getIgnoredTestResults(), scenarioIndex); } + + if (reportKnownIssueTestCases) { + scenarioIndex += writeScenarioDetails(testResult.getKnownIssueTestResults(), scenarioIndex); + } scenarioIndex += writeScenarioDetails(testResult.getFailedConfigurationResults(), scenarioIndex); scenarioIndex += writeScenarioDetails(testResult.getFailedTestResults(), scenarioIndex); scenarioIndex += writeScenarioDetails(testResult.getSkippedConfigurationResults(), scenarioIndex); @@ -836,7 +907,9 @@ public int compare(ITestResult o1, ITestResult o2) { private final List skippedTestResults; private final List passedTestResults; private final List ignoredTestResults; + private final List knownIssueTestResults; private final int ignoredTestCount; + private final int knownIssueTestCount; private final int failedTestCount; private final int skippedTestCount; private final int passedTestCount; @@ -855,6 +928,7 @@ public TestResult(ITestContext context) { // Set skippedTests = context.getSkippedTests().getAllResults(); Set skippedTests = getResultsSubSet(context.getSkippedTests().getAllResults(), GlobalConstants.SKIPPED); Set ignoredTests = getResultsSubSet(context.getSkippedTests().getAllResults(), GlobalConstants.IGNORED_SUBSET_STRING); + Set knownIssueTests = getResultsSubSet(context.getSkippedTests().getAllResults(), GlobalConstants.KNOWN_ISSUE_SUBSET_STRING); Set passedTests = context.getPassedTests().getAllResults(); failedConfigurationResults = groupResults(failedConfigurations); @@ -862,12 +936,14 @@ public TestResult(ITestContext context) { skippedConfigurationResults = groupResults(skippedConfigurations); skippedTestResults = groupResults(skippedTests); ignoredTestResults = groupResults(ignoredTests); + knownIssueTestResults = groupResults(knownIssueTests); passedTestResults = groupResults(passedTests); failedTestCount = failedTests.size(); skippedTestCount = skippedTests.size(); passedTestCount = passedTests.size(); ignoredTestCount = ignoredTests.size(); + knownIssueTestCount = knownIssueTests.size(); duration = context.getEndDate().getTime() - context.getStartDate().getTime(); @@ -954,6 +1030,10 @@ public List getFailedTestResults() { public List getIgnoredTestResults() { return ignoredTestResults; } + + public List getKnownIssueTestResults() { + return knownIssueTestResults; + } /** * @return the results for skipped configurations (possibly empty) @@ -991,6 +1071,10 @@ public int getPassedTestCount() { public int getIgnoredTestCount() { return ignoredTestCount; } + + public int getKnownIssueTestCount() { + return knownIssueTestCount; + } public long getDuration() { return duration; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/ExtractResource.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/ExtractResource.java index cbdee0a36e..92379bfe50 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/ExtractResource.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/ExtractResource.java @@ -21,6 +21,7 @@ public static void extractCommonResourceFromJar() { getListOfFilesFromJarAndCopyToExternalResource("spring.properties"); getListOfFilesFromJarAndCopyToExternalResource("validations.properties"); getListOfFilesFromJarAndCopyToExternalResource("dbFiles/"); + getListOfFilesFromJarAndCopyToExternalResource("testCaseSkippedList.txt"); } public static void copyCommonResources(){ @@ -31,6 +32,7 @@ public static void copyCommonResources(){ copyCommonResources("spring.properties"); copyCommonResources("validations.properties"); copyCommonResources("dbFiles/"); + copyCommonResources("testCaseSkippedList.txt"); } public static void copyCommonResources(String moduleName){ diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java index 964ec364cf..b12b363c4a 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ConfigManager.java @@ -70,6 +70,7 @@ public class ConfigManager { private static String PUSH_TO_S3 = "push-reports-to-s3"; private static String ENABLE_DEBUG = "enableDebug"; private static String REPORT_IGNORED_TEST_CASES = "reportIgnoredTestCases"; + private static String REPORT_KNOWN_ISSUE_TEST_CASES = "reportKnownIssueTestCases"; private static String THREAD_COUNT = "threadCount"; private static String LANG_SELECT = "langselect"; @@ -201,6 +202,7 @@ public class ConfigManager { private static String push_reports_to_s3; private static String enableDebug; private static String reportIgnoredTestCases; + private static String reportKnownIssueTestCases; private static String threadCount; private static String langselect; private static String usePreConfiguredOtp; @@ -396,6 +398,11 @@ public static void init() { ? propsKernel.getProperty(REPORT_IGNORED_TEST_CASES) : System.getenv(REPORT_IGNORED_TEST_CASES); propsKernel.setProperty(REPORT_IGNORED_TEST_CASES, reportIgnoredTestCases); + + reportKnownIssueTestCases = System.getenv(REPORT_KNOWN_ISSUE_TEST_CASES) == null + ? propsKernel.getProperty(REPORT_KNOWN_ISSUE_TEST_CASES) + : System.getenv(REPORT_KNOWN_ISSUE_TEST_CASES); + propsKernel.setProperty(REPORT_KNOWN_ISSUE_TEST_CASES, reportKnownIssueTestCases); threadCount = System.getenv(THREAD_COUNT) == null ? propsKernel.getProperty(THREAD_COUNT) : System.getenv(THREAD_COUNT); @@ -575,6 +582,10 @@ public static Boolean IsDebugEnabled() { public static Boolean reportIgnoredTestCases() { return reportIgnoredTestCases.equalsIgnoreCase("yes"); } + + public static Boolean reportKnownIssueTestCases() { + return reportKnownIssueTestCases.equalsIgnoreCase("yes"); + } public static String getReportExpirationInDays() { return reportExpirationInDays; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java index 57b3bf2e48..ea7f726ca6 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/GlobalConstants.java @@ -217,7 +217,10 @@ public class GlobalConstants { public static final String SERVICE_NOT_DEPLOYED_MESSAGE = "Service not deployed. Hence skipping the testcase"; public static final String FEATURE_NOT_SUPPORTED = "feature not supported"; public static final String SERVICE_NOT_DEPLOYED = "Service not deployed"; + public static final String KNOWN_ISSUES_STRING = "known issue"; + public static final String KNOWN_ISSUES = "known issue. Hence skipping the testcase"; public static final String IGNORED_SUBSET_STRING = "feature not supported;Service not deployed"; + public static final String KNOWN_ISSUE_SUBSET_STRING = "known issue;"; public static final String SKIPPED = "Skipped"; public static final String TARGET_ENV_HEALTH_CHECK_FAILED = "Target env health check failed "; public static final String HOTLIST = "hotlist"; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/SkipTestCaseHandler.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/SkipTestCaseHandler.java new file mode 100644 index 0000000000..7d48904e26 --- /dev/null +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/SkipTestCaseHandler.java @@ -0,0 +1,62 @@ +package io.mosip.testrig.apirig.utils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import io.mosip.testrig.apirig.testrunner.MosipTestRunner; + +public class SkipTestCaseHandler { + private static final Logger logger = Logger.getLogger(SkipTestCaseHandler.class); + private static List testcaseToBeSkippedList = new ArrayList<>(); + + public static void main(String[] args) { + String filePath = "C:\\Users\\sivan\\Downloads\\SynData\\testCaseSkippedList.txt"; // Replace with your file + // path + + loadTestcaseToBeSkippedList(filePath); + + // Example usage of the checkStringInList method + String searchString = "IdRepository_UpdateIdentity_handle_value_value_withupdatevalues"; + boolean exists = isTestCaseInSkippedList(searchString); + logger.info("Does the string exist? " + exists); + + searchString = "IdRepository_UpdateIdentity_handle_value_value_withupdatevalues"; + exists = isTestCaseInSkippedList(searchString); + logger.info("Does the string exist? " + exists); + + } + + // load test cases to be skipped in the execution in the list + public static void loadTestcaseToBeSkippedList(String fileName) { + try (BufferedReader br = new BufferedReader( + new FileReader(MosipTestRunner.getGlobalResourcePath() + "/" + fileName))) { + String line; + while ((line = br.readLine()) != null) { + // Ignore lines starting with # as it is commented line + if (line.startsWith("#")) { + continue; + } + + // Split the line by "------" and store the second part + if (line.contains("------")) { + String[] parts = line.split("------"); + if (parts.length > 1) { + testcaseToBeSkippedList.add(parts[1].trim()); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Method to check if a given test case exists in the list + public static boolean isTestCaseInSkippedList(String strTestCase) { + return testcaseToBeSkippedList.contains(strTestCase); + } +} \ No newline at end of file From 5de5ac6d2652564547ae8f0d6ed24d99b4c1cc37 Mon Sep 17 00:00:00 2001 From: Mohan E Date: Tue, 17 Sep 2024 17:16:35 +0530 Subject: [PATCH 31/43] [MOSIP-35421] Moved helm charts and installtion scripts of apitestrig and uitestrig. Signed-off-by: Mohanraj209 --- deploy/apitestrig/README.md | 4 ++-- deploy/uitestrig/README.md | 12 +++++++++++- .../images => docs/_images}/apitestrig-1.png | Bin .../images => docs/_images}/apitestrig-2.png | Bin docs/_images/ui-testrig.png | Bin 0 -> 86339 bytes 5 files changed, 13 insertions(+), 3 deletions(-) rename {deploy/apitestrig/images => docs/_images}/apitestrig-1.png (100%) rename {deploy/apitestrig/images => docs/_images}/apitestrig-2.png (100%) create mode 100644 docs/_images/ui-testrig.png diff --git a/deploy/apitestrig/README.md b/deploy/apitestrig/README.md index 9942e3cc69..ef7619b8ae 100644 --- a/deploy/apitestrig/README.md +++ b/deploy/apitestrig/README.md @@ -24,7 +24,7 @@ ApiTestRig will test the working of APIs of the MOSIP modules. #### Rancher UI * Run apitestrig manually via Rancher UI. - ![apitestrig-2.png](./images/apitestrig-2.png) + ![apitestrig-2.png](../../docs/_images/apitestrig-2.png) * There are two modes of apitestrig `smoke` & `smokeAndRegression`. * By default, apitestrig will execute with `smokeAndRegression`.
If you want to run apitestrig with only `smoke`.
@@ -32,7 +32,7 @@ ApiTestRig will test the working of APIs of the MOSIP modules. #### CLI * Download Kubernetes cluster `kubeconfig` file from `rancher dashboard` to your local. - ![apitestrig-1.png](./images/apitestrig-1.png) + ![apitestrig-1.png](../../docs/_images/apitestrig-1.png) * Install `kubectl` package to your local machine. * Run apitestrig manually via CLI by creating a new job from an existing k8s cronjob. ``` diff --git a/deploy/uitestrig/README.md b/deploy/uitestrig/README.md index 00b2177faf..c1dc01550b 100644 --- a/deploy/uitestrig/README.md +++ b/deploy/uitestrig/README.md @@ -29,4 +29,14 @@ UITESTRIG will test end-to-end functional flows involving multiple UI modules. kubectl --kubeconfig=/home/xxx/Downloads/qa4.config -n UITESTRIG create job --from=cronjob/cronjob-uitestrig cronjob-uitestrig ``` - +## Run ui-testrig using rancher-ui + +#### Rancher UI +* Run ui-testrig manually via Rancher UI. + ![ui-testrig.png](../../docs/_images/ui-testrig.png) +* There are 3 cronjobs for ui-testrig: + - adminui + - pmp-ui + - residentui +* select the cronjob which you want to initiate and select the `Run Now` button as shown in the above picture. +* Once the job is completed the reports will be avaiable in `S3/minio`. diff --git a/deploy/apitestrig/images/apitestrig-1.png b/docs/_images/apitestrig-1.png similarity index 100% rename from deploy/apitestrig/images/apitestrig-1.png rename to docs/_images/apitestrig-1.png diff --git a/deploy/apitestrig/images/apitestrig-2.png b/docs/_images/apitestrig-2.png similarity index 100% rename from deploy/apitestrig/images/apitestrig-2.png rename to docs/_images/apitestrig-2.png diff --git a/docs/_images/ui-testrig.png b/docs/_images/ui-testrig.png new file mode 100644 index 0000000000000000000000000000000000000000..d2010016f7b8af9fef70abffbed770b864be0f27 GIT binary patch literal 86339 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≠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 literal 0 HcmV?d00001 From dee876b61411652eb3e85bddb6bda52a0bd412db Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 18 Sep 2024 08:07:35 +0000 Subject: [PATCH 32/43] fix: authentication-demo-service/pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-ORGAPACHECOMMONS-3043138 - https://snyk.io/vuln/SNYK-JAVA-ORGJSON-5488379 - https://snyk.io/vuln/SNYK-JAVA-ORGJSON-5962464 - https://snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORK-6597980 Signed-off-by: rajapandi.m --- authentication-demo-service/pom.xml | 778 ++++++++++++++-------------- 1 file changed, 389 insertions(+), 389 deletions(-) diff --git a/authentication-demo-service/pom.xml b/authentication-demo-service/pom.xml index 89e048b3be..1f9fa8d852 100644 --- a/authentication-demo-service/pom.xml +++ b/authentication-demo-service/pom.xml @@ -1,391 +1,391 @@ - 4.0.0 - - - io.mosip.testrig.authentication.demo - 1.2.1-SNAPSHOT - authentication-demo-service - authentication-demo-service - Parent project of MOSIP functional tests - https://github.com/mosip/mosip-functional-tests - jar - - - - MPL 2.0 - https://www.mozilla.org/en-US/MPL/2.0/ - - - - - scm:git:git://github.com/mosip/mosip-functional-tests.git - scm:git:ssh://github.com:mosip/mosip-functional-tests.git - https://github.com/mosip/mosip-functional-tests - HEAD - - - - - Mosip - mosip.emailnotifier@gmail.com - io.mosip - https://github.com/mosip/mosip-functional-tests - - - - - - local-maven-repo - file:///${basedir}/lib - - - - snapshots-repo - https://oss.sonatype.org/content/repositories/snapshots - false - true - - - - releases-repo - https://oss.sonatype.org/service/local/staging/deploy/maven2 - false - false - - - - danubetech-maven-public - https://repo.danubetech.com/repository/maven-public/ - - - - - - 3.2.3 - 2.0.0.RELEASE - - 21 - 21 - 3.8.0 - 3.0.1 - 2.9.2 - 0.8.2 - - 1.2.1-SNAPSHOT - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - - - commons-io - commons-io - - - javax.ws.rs - javax.ws.rs-api - 2.0 - - - org.glassfish.jersey.core - jersey-common - test - - - io.mosip.authentication - authentication-core - 1.2.1-SNAPSHOT - - - commons-codec - commons-codec - - - org.springdoc - springdoc-openapi-ui - - - - - commons-fileupload - commons-fileupload - - - - org.mockito - mockito-core - - - - io.mosip.kernel - kernel-core - 1.2.1-SNAPSHOT - - - org.springframework.boot - - spring-boot-starter-security - - - - commons-codec - commons-codec - - - - - io.mosip.kernel - kernel-websubclient-api - ${kernel-websubclient-api.version} - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.apache.httpcomponents - httpclient - - - commons-codec - commons-codec - - - - - - - org.apache.httpcomponents - httpmime - 4.5.7 - - - org.json - json - 20180813 - - - org.apache.commons - commons-lang3 - - - org.tensorflow - tensorflow - 1.12.0 - - - - org.apache.commons - commons-io - 1.3.2 - - - - - commons-lang - commons-lang - 2.4 - - - org.apache.commons - commons-text - 1.6 - - - commons-codec - commons-codec - - - io.mosip.kernel - kernel-templatemanager-velocity - 1.2.1-SNAPSHOT - - - org.springframework.cloud - spring-cloud-starter-config - - - org.postgresql - postgresql - - - com.github.jai-imageio - jai-imageio-jpeg2000 - 1.3.0 - - - org.json - json - 20180813 - - - io.mosip.kernel - kernel-keymanager-service - 1.2.1-SNAPSHOT - lib - - - org.springframework.boot - - spring-boot-starter-security - - - - - - dom4j - dom4j - 1.6.1 - - - org.junit.vintage - junit-vintage-engine - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.5.0 - - - - - - io.mosip.kernel - kernel-bom - 1.2.1-SNAPSHOT - pom - import - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - none - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - true - ZIP - - - - - build-info - repackage - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - --pinentry-mode - loopback - - - - - - - pl.project13.maven - git-commit-id-plugin - 3.0.1 - - - get-the-git-infos - - revision - - validate - - - - true - ${project.build.outputDirectory}/service-git.properties - - ^git.build.(time|version)$ - ^git.commit.id.(abbrev|full)$ - - full - ${project.basedir}/.git - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven.compiler.version} - - ${maven.compiler.source} - ${maven.compiler.target} - - - - org.jacoco - jacoco-maven-plugin - - - **/constant/** - **/config/** - **/dto/** - io/mosip/authentication/service/*.class - io/mosip/authentication/service/filter/** - io/mosip/demo/authentication/service/IdAuthenticationDemoApplication.class - - - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + + io.mosip.testrig.authentication.demo + 1.2.1-SNAPSHOT + authentication-demo-service + authentication-demo-service + Parent project of MOSIP functional tests + https://github.com/mosip/mosip-functional-tests + jar + + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + + + + + scm:git:git://github.com/mosip/mosip-functional-tests.git + scm:git:ssh://github.com:mosip/mosip-functional-tests.git + https://github.com/mosip/mosip-functional-tests + HEAD + + + + + Mosip + mosip.emailnotifier@gmail.com + io.mosip + https://github.com/mosip/mosip-functional-tests + + + + + + local-maven-repo + file:///${basedir}/lib + + + + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots + false + true + + + + releases-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2 + false + false + + + + danubetech-maven-public + https://repo.danubetech.com/repository/maven-public/ + + + + + + 3.2.3 + 2.0.0.RELEASE + + 21 + 21 + 3.8.0 + 3.0.1 + 2.9.2 + 0.8.2 + + 1.2.1-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + commons-io + commons-io + + + javax.ws.rs + javax.ws.rs-api + 2.0 + + + org.glassfish.jersey.core + jersey-common + test + + + io.mosip.authentication + authentication-core + 1.2.1-SNAPSHOT + + + commons-codec + commons-codec + + + org.springdoc + springdoc-openapi-ui + + + + + commons-fileupload + commons-fileupload + + + + org.mockito + mockito-core + + + + io.mosip.kernel + kernel-core + 1.2.1-SNAPSHOT + + + org.springframework.boot + + spring-boot-starter-security + + + + commons-codec + commons-codec + + + + + io.mosip.kernel + kernel-websubclient-api + ${kernel-websubclient-api.version} + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.apache.httpcomponents + httpclient + + + commons-codec + commons-codec + + + + + + + org.apache.httpcomponents + httpmime + 4.5.7 + + + org.json + json + 20231013 + + + org.apache.commons + commons-lang3 + + + org.tensorflow + tensorflow + 1.12.0 + + + + org.apache.commons + commons-io + 1.3.2 + + + + + commons-lang + commons-lang + 2.4 + + + org.apache.commons + commons-text + 1.10.0 + + + commons-codec + commons-codec + + + io.mosip.kernel + kernel-templatemanager-velocity + 1.2.1-SNAPSHOT + + + org.springframework.cloud + spring-cloud-starter-config + + + org.postgresql + postgresql + + + com.github.jai-imageio + jai-imageio-jpeg2000 + 1.3.0 + + + org.json + json + 20231013 + + + io.mosip.kernel + kernel-keymanager-service + 1.2.1-SNAPSHOT + lib + + + org.springframework.boot + + spring-boot-starter-security + + + + + + dom4j + dom4j + 1.6.1 + + + org.junit.vintage + junit-vintage-engine + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + + + + + + io.mosip.kernel + kernel-bom + 1.2.1-SNAPSHOT + pom + import + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + none + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + true + ZIP + + + + + build-info + repackage + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + pl.project13.maven + git-commit-id-plugin + 3.0.1 + + + get-the-git-infos + + revision + + validate + + + + true + ${project.build.outputDirectory}/service-git.properties + + ^git.build.(time|version)$ + ^git.commit.id.(abbrev|full)$ + + full + ${project.basedir}/.git + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.jacoco + jacoco-maven-plugin + + + **/constant/** + **/config/** + **/dto/** + io/mosip/authentication/service/*.class + io/mosip/authentication/service/filter/** + io/mosip/demo/authentication/service/IdAuthenticationDemoApplication.class + + + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + \ No newline at end of file From 1d036f6386accf9140552d8961d3ed2a91839bd9 Mon Sep 17 00:00:00 2001 From: Mohan E Date: Wed, 18 Sep 2024 16:24:26 +0530 Subject: [PATCH 33/43] [MOSIP-35421] Moved helm charts and installtion scripts of apitestrig and uitestrig. Signed-off-by: Mohanraj209 --- deploy/apitestrig/values.yaml | 54 +++++++++++++++++++++++++++++------ deploy/uitestrig/install.sh | 1 + deploy/uitestrig/values.yaml | 22 ++++++++++++++ 3 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 deploy/uitestrig/values.yaml diff --git a/deploy/apitestrig/values.yaml b/deploy/apitestrig/values.yaml index dc15566f41..fd2bca2911 100644 --- a/deploy/apitestrig/values.yaml +++ b/deploy/apitestrig/values.yaml @@ -1,17 +1,55 @@ modules: prereg: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-prereg + tag: develop + pullPolicy: Always masterdata: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-masterdata + tag: develop + pullPolicy: Always idrepo: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-idrepo + tag: develop + pullPolicy: Always partner: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-pms + tag: develop + pullPolicy: Always + pms: + enabled: false + image: + repository: mosipdev/apitest-pms + tag: develop + pullPolicy: Always resident: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-resident + tag: develop + pullPolicy: Always auth: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-auth + tag: develop + pullPolicy: Always esignet: - enabled: true + enabled: false + image: + repository: mosipqa/apitest-esignet + tag: develop + pullPolicy: Always mimoto: - enabled: true \ No newline at end of file + enabled: false + image: + repository: mosipqa/apitest-mimoto + tag: develop + pullPolicy: Always diff --git a/deploy/uitestrig/install.sh b/deploy/uitestrig/install.sh index d1f48939ed..98c0577ff1 100644 --- a/deploy/uitestrig/install.sh +++ b/deploy/uitestrig/install.sh @@ -63,6 +63,7 @@ function installing_uitestrig() { echo Installing uitestrig helm -n $NS install uitestrig mosip/uitestrig \ --set crontime="0 $time * * *" \ + -f values.yaml \ --version $CHART_VERSION \ --set uitestrig.configmaps.s3.s3-host='http://minio.minio:9000' \ --set uitestrig.configmaps.s3.s3-user-key='admin' \ diff --git a/deploy/uitestrig/values.yaml b/deploy/uitestrig/values.yaml new file mode 100644 index 0000000000..014727441d --- /dev/null +++ b/deploy/uitestrig/values.yaml @@ -0,0 +1,22 @@ +modules: + - name: admin-ui + enabled: false + image: + registry: docker.io + repository: mosipqa/uitest-admin + tag: develop + pullPolicy: Always + - name: pmp-ui + enabled: false + image: + registry: docker.io + repository: mosipqa/uitest-pmp + tag: develop + pullPolicy: Always + - name: resident-ui + enabled: false + image: + registry: docker.io + repository: mosipqa/uitest-resident + tag: develop + pullPolicy: Always From 170f160b7eeb8688679926e081587bf599bb25c0 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Thu, 19 Sep 2024 11:48:29 +0530 Subject: [PATCH 34/43] MOSIP-35792 - Identity handled for name value from schema Signed-off-by: Mohanachandran S --- .../io/mosip/testrig/apirig/utils/AdminTestUtil.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java index c6e9e4bdef..47e113adca 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/AdminTestUtil.java @@ -3744,10 +3744,14 @@ public String inputJsonKeyWordHandeler(String jsonString, String testCaseName) { } if (jsonString.contains("$NAMEFORUPDATEUIN$")) { - String name = getValueFromAuthActuator("json-property", "name"); - String nameResult = name.replaceAll("\\[\"|\"\\]", ""); - + String nameResult = ""; + + if (new JSONArray(name).length() > 1) { + nameResult = new JSONArray(name).getString(0); + }else { + nameResult = name.replaceAll("\\[\"|\"\\]", ""); + } jsonString = replaceKeywordWithValue(jsonString, "$NAMEFORUPDATEUIN$", nameResult); } From 24c38cf6fe7fc691460405d99c254dd6bb4c36fe Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Fri, 20 Sep 2024 11:48:25 +0530 Subject: [PATCH 35/43] MOSIP-35886 - Removing kernel cypto library and added the class in testrig Signed-off-by: Mohanachandran S --- apitest-commons/pom.xml | 23 - .../apirig/admin/fw/config/BeanConfig.java | 3 - .../apirig/encrypt/util/CryptoCore.java | 549 ++++++++++++++++++ .../apirig/encrypt/util/CryptoUtils.java | 58 ++ .../util/SecurityExceptionCodeConstant.java | 71 +++ .../apirig/utils/BiometricDataUtility.java | 6 +- .../testrig/apirig/utils/CryptoUtil.java | 3 +- 7 files changed, 683 insertions(+), 30 deletions(-) create mode 100644 apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java create mode 100644 apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java create mode 100644 apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index 7e5aeb7fa6..6d6fdf27b3 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -335,29 +335,6 @@ javax.mail-api 1.6.2 - - - - - io.mosip.kernel - kernel-registration-packet-manager - 1.1.5.3 - - - org.springframework - spring-expression - - - org.bouncycastle - bcprov-jdk15on - - - - - io.mosip.kernel - kernel-crypto-jce - 1.1.5.3 - org.apache.wink wink-json4j-provider diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java index a162a2198b..03ce78fc57 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java @@ -2,12 +2,9 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import io.mosip.kernel.crypto.jce.core.CryptoCore; @Configuration -@Import({ CryptoCore.class }) @ComponentScan(basePackages = { "io.mosip.testrig.apirig", "io.mosip.testrig.dslrig"}) public class BeanConfig { int i = 0; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java new file mode 100644 index 0000000000..6e50dc5707 --- /dev/null +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java @@ -0,0 +1,549 @@ +package io.mosip.testrig.apirig.encrypt.util; + +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.MGF1ParameterSpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import jakarta.annotation.PostConstruct; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PSource.PSpecified; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.encodings.OAEPEncoding; +import org.bouncycastle.crypto.engines.RSAEngine; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwx.CompactSerializer; +import org.jose4j.lang.JoseException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.mosip.kernel.core.crypto.exception.InvalidDataException; +import io.mosip.kernel.core.crypto.exception.InvalidKeyException; +import io.mosip.kernel.core.crypto.exception.InvalidParamSpecException; +import io.mosip.kernel.core.crypto.exception.SignatureException; +import io.mosip.kernel.core.crypto.spi.CryptoCoreSpec; +import io.mosip.kernel.core.exception.NoSuchAlgorithmException; +import io.mosip.kernel.core.util.CryptoUtil; +import io.mosip.kernel.core.util.EmptyCheckUtils; +import io.mosip.kernel.crypto.jce.constant.SecurityExceptionCodeConstant; +import io.mosip.kernel.crypto.jce.util.CryptoUtils; + +/** + * This class provided Basic and Core Cryptographic functionalities . + * + * This class follows {@link CryptoCoreSpec} and implement all basic + * Cryptographic functions. + * + * @author Urvil Joshi + * @author Rajath + * @since 1.0.0 + * + * @see CryptoCoreSpec + * @see PrivateKey + * @see PublicKey + * @see SecretKey + * @see Cipher + * @see GCMParameterSpec + * @see SecureRandom + */ +//Code optimization remaining (Code Dupe) +@Component +public class CryptoCore implements CryptoCoreSpec { + + private static final String PERIOD_SEPARATOR_REGEX = "\\."; + + // Used as a hack for softhsm oeap padding decryption usecase will be when we + // will use in HSM + @SuppressWarnings("java:S106") + private static final String RSA_ECB_NO_PADDING = "RSA/ECB/NoPadding"; // NOSONAR using the padding for allowing OAEP padding in PKCS11 library + + private static final String PKCS11_STORE_TYPE = "PKCS11"; + + @Value("${mosip.kernel.keygenerator.asymmetric-key-length:2048}") + private int asymmetricKeyLength; + + private static final String MGF1 = "MGF1"; + + private static final String HASH_ALGO = "SHA-256"; + + private static final String AES = "AES"; + + @Value("${mosip.kernel.crypto.gcm-tag-length:128}") + private int tagLength; + + @Value("${mosip.automation.crypto.symmetric-algorithm-name:AES/GCM/NoPadding}") + private String symmetricAlgorithm; + + @Value("${mosip.kernel.crypto.asymmetric-algorithm-name:RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING}") + private String asymmetricAlgorithm; + + @Value("${mosip.kernel.crypto.hash-algorithm-name:PBKDF2WithHmacSHA512}") + private String passwordAlgorithm; + + @Value("${mosip.kernel.crypto.sign-algorithm-name:RS256}") + private String signAlgorithm; + + @Value("${mosip.kernel.crypto.hash-symmetric-key-length:256}") + private int symmetricKeyLength; + + @Value("${mosip.kernel.crypto.hash-iteration:100000}") + private int iterations; + + @Value("${mosip.kernel.keymanager.hsm.keystore-type:PKCS11}") + private String keystoreType; + + private SecureRandom secureRandom; + + @PostConstruct + public void init() { + secureRandom = new SecureRandom(); + } + + @Override + public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Cipher.getInstance(symmetricAlgorithm); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + byte[] output = null; + byte[] randomIV = generateIV(cipher.getBlockSize()); + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, randomIV); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); + output = new byte[cipher.getOutputSize(data.length) + cipher.getBlockSize()]; + if (aad != null && aad.length != 0) { + cipher.updateAAD(aad); + } + byte[] processData = doFinal(data, cipher); + System.arraycopy(processData, 0, output, 0, processData.length); + System.arraycopy(randomIV, 0, output, processData.length, randomIV.length); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + return output; + } + + @Override + public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + if (iv == null) { + return symmetricEncrypt(key, data, aad); + } + Cipher cipher; + try { + cipher = Cipher.getInstance(symmetricAlgorithm); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, iv); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); + if (aad != null && aad.length != 0) { + cipher.updateAAD(aad); + } + return doFinal(data, cipher); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + @Override + public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Cipher.getInstance(symmetricAlgorithm); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + byte[] output = null; + try { + byte[] randomIV = Arrays.copyOfRange(data, data.length - cipher.getBlockSize(), data.length); + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, randomIV); + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); + if (aad != null && aad.length != 0) { + cipher.updateAAD(aad); + } + output = doFinal(Arrays.copyOf(data, data.length - cipher.getBlockSize()), cipher); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorMessage(), e); + } + return output; + } + + @Override + public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + if (iv == null) { + return symmetricDecrypt(key, data, aad); + } + Cipher cipher; + try { + cipher = Cipher.getInstance(symmetricAlgorithm); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, iv); + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); + if (aad != null) { + cipher.updateAAD(aad); + } + return doFinal(data, cipher); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + @Override + public byte[] asymmetricEncrypt(PublicKey key, byte[] data) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Cipher.getInstance(asymmetricAlgorithm); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + final OAEPParameterSpec oaepParams = new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, + PSpecified.DEFAULT); + try { + cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + return doFinal(data, cipher); + } + + @Override + public byte[] asymmetricDecrypt(PrivateKey privateKey, byte[] data) { + if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { + BigInteger keyModulus = ((RSAPrivateKey) privateKey).getModulus(); + return asymmetricDecrypt(privateKey, keyModulus, data, null); + } + return jceAsymmetricDecrypt(privateKey, data, null); + } + + @Override + public byte[] asymmetricDecrypt(PrivateKey privateKey, PublicKey publicKey, byte[] data) { + if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { + BigInteger keyModulus = Objects.nonNull(publicKey) ? ((RSAPublicKey) publicKey).getModulus() : + ((RSAPrivateKey) privateKey).getModulus(); + return asymmetricDecrypt(privateKey, keyModulus, data, null); + } + return jceAsymmetricDecrypt(privateKey, data, null); + } + + @Override + public byte[] asymmetricDecrypt(PrivateKey privateKey, PublicKey publicKey, byte[] data, String storeType) { + if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { + BigInteger keyModulus = Objects.nonNull(publicKey) ? ((RSAPublicKey) publicKey).getModulus() : + ((RSAPrivateKey) privateKey).getModulus(); + return asymmetricDecrypt(privateKey, keyModulus, data, storeType); + } + return jceAsymmetricDecrypt(privateKey, data, storeType); + } + + private byte[] asymmetricDecrypt(PrivateKey privateKey, BigInteger keyModulus, byte[] data, String storeType) { + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Objects.isNull(storeType) ? Cipher.getInstance(RSA_ECB_NO_PADDING) : // NOSONAR using the padding for allowing OAEP padding in PKCS11 library + Cipher.getInstance(RSA_ECB_NO_PADDING, storeType); // NOSONAR using the padding for allowing OAEP padding in PKCS11 library + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + + try { + cipher.init(Cipher.DECRYPT_MODE, privateKey); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + /* + * This is a hack of removing OEAP padding after decryption with NO Padding as + * SoftHSM does not support it.Will be removed after HSM implementation + */ + byte[] paddedPlainText = doFinal(data, cipher); + if (paddedPlainText.length < asymmetricKeyLength / 8) { + byte[] tempPipe = new byte[asymmetricKeyLength / 8]; + System.arraycopy(paddedPlainText, 0, tempPipe, tempPipe.length - paddedPlainText.length, + paddedPlainText.length); + paddedPlainText = tempPipe; + } + + return unpadOAEPPadding(paddedPlainText, keyModulus); + } + + // This is a hack of removing OEAP padding after decryption with NO Padding as + // SoftHSM does not support it.Will be removed after HSM implementation + /** + * + * @param paddedPlainText + * @param privateKey + * @return + */ + private byte[] unpadOAEPPadding(byte[] paddedPlainText, BigInteger keyModulus) { + + try { + OAEPEncoding encode = new OAEPEncoding(new RSAEngine(), new SHA256Digest()); + BigInteger exponent = new BigInteger("1"); + RSAKeyParameters keyParams = new RSAKeyParameters(false, keyModulus, exponent); + encode.init(false, keyParams); + return encode.processBlock(paddedPlainText, 0, paddedPlainText.length); + } catch (InvalidCipherTextException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION + .getErrorCode(), e.getMessage(), e); + } + } + + private byte[] jceAsymmetricDecrypt(PrivateKey privateKey, byte[] data, String storeType){ + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Objects.isNull(storeType) ? Cipher.getInstance(asymmetricAlgorithm) : + Cipher.getInstance(asymmetricAlgorithm, storeType); + OAEPParameterSpec oaepParams = new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, + PSpecified.DEFAULT); + cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams); + return doFinal(data, cipher); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + + @Override + public String hash(byte[] data, byte[] salt) { + CryptoUtils.verifyData(data); + CryptoUtils.verifyData(salt, SecurityExceptionCodeConstant.SALT_PROVIDED_IS_NULL_OR_EMPTY.getErrorCode(), + SecurityExceptionCodeConstant.SALT_PROVIDED_IS_NULL_OR_EMPTY.getErrorMessage()); + SecretKeyFactory secretKeyFactory; + char[] convertedData = new String(data).toCharArray(); + PBEKeySpec pbeKeySpec = new PBEKeySpec(convertedData, salt, iterations, symmetricKeyLength); + SecretKey key; + try { + secretKeyFactory = SecretKeyFactory.getInstance(passwordAlgorithm); + key = secretKeyFactory.generateSecret(pbeKeySpec); + } catch (InvalidKeySpecException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), e.getMessage(), e); + } catch (java.security.NoSuchAlgorithmException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + return DatatypeConverter.printHexBinary(key.getEncoded()); + } + + @Override + public String sign(byte[] data, PrivateKey privateKey) { + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayloadBytes(data); + jws.setAlgorithmHeaderValue(signAlgorithm); + jws.setKey(privateKey); + jws.setDoKeyValidation(false); + try { + return jws.getDetachedContentCompactSerialization(); + } catch (JoseException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + @Override + public boolean verifySignature(byte[] data, String sign, PublicKey publicKey) { + if (EmptyCheckUtils.isNullEmpty(sign)) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorMessage()); + } + Objects.requireNonNull(publicKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + JsonWebSignature jws = new JsonWebSignature(); + try { + String[] parts = sign.split(PERIOD_SEPARATOR_REGEX); + parts[1] = CryptoUtil.encodeBase64(data); + jws.setCompactSerialization(CompactSerializer.serialize(parts)); + jws.setKey(publicKey); + return jws.verifySignature(); + } catch (ArrayIndexOutOfBoundsException | JoseException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + + } + + @SuppressWarnings("unchecked") + @Override + public SecureRandom random() { + return secureRandom; + } + + /** + * Generator for IV(Initialisation Vector) + * + * @param blockSize blocksize of current cipher + * @return generated IV + */ + private byte[] generateIV(int blockSize) { + byte[] byteIV = new byte[blockSize]; + secureRandom.nextBytes(byteIV); + return byteIV; + } + + private byte[] doFinal(byte[] data, Cipher cipher) { + try { + return cipher.doFinal(data); + } catch (IllegalBlockSizeException e) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_SIZE_EXCEPTION.getErrorCode(), e.getMessage(), e); + } catch (BadPaddingException e) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_ENCRYPTED_DATA_CORRUPT_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + /* + * This two methods here are for temporary, Unit test for this will be written + * in next versions + */ + @Override + public String sign(byte[] data, PrivateKey privateKey, X509Certificate x509Certificate) { + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + JsonWebSignature jws = new JsonWebSignature(); + List certList = new ArrayList<>(); + certList.add(x509Certificate); + X509Certificate[] certArray = certList.toArray(new X509Certificate[] {}); + jws.setCertificateChainHeaderValue(certArray); + jws.setPayloadBytes(data); + jws.setAlgorithmHeaderValue(signAlgorithm); + jws.setKey(privateKey); + jws.setDoKeyValidation(false); + try { + return jws.getCompactSerialization(); + } catch (JoseException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + /* + * This two methods here are for temporary, Unit test for this will be written + * in next versions + */ + @Override + public boolean verifySignature(String sign) { + if (EmptyCheckUtils.isNullEmpty(sign)) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorMessage()); + } + JsonWebSignature jws = new JsonWebSignature(); + try { + jws.setCompactSerialization(sign); + List certificateChainHeaderValue = jws.getCertificateChainHeaderValue(); + X509Certificate certificate = certificateChainHeaderValue.get(0); + certificate.checkValidity(); + PublicKey publicKey = certificate.getPublicKey(); + jws.setKey(publicKey); + return jws.verifySignature(); + } catch (JoseException | CertificateExpiredException | CertificateNotYetValidException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + + } + + +} diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java new file mode 100644 index 0000000000..4355774c25 --- /dev/null +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java @@ -0,0 +1,58 @@ +/* + * + * + * + * + * + */ +package io.mosip.testrig.apirig.encrypt.util; + +import io.mosip.kernel.core.crypto.exception.InvalidDataException; +import io.mosip.kernel.core.crypto.exception.NullDataException; +import io.mosip.kernel.crypto.jce.constant.SecurityExceptionCodeConstant; + +/** + * Utility class for crypto + * + * @author Urvil Joshi + * @since 1.0.0 + */ +public class CryptoUtils { + + /** + * Constructor for this class + */ + private CryptoUtils() { + + } + + /** + * Verify if data is null or empty + * + * @param + * + * @param data data provided by user + */ + public static void verifyData(byte[] data) { + if (data == null) { + throw new NullDataException(SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorMessage()); + } else if (data.length == 0) { + throw new InvalidDataException(SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorMessage()); + } + } + + /** + * Verify if data is null or empty + * + * @param data data provided by user + */ + public static void verifyData(byte[] data, String errorCode, String message) { + if (data == null) { + throw new NullDataException(errorCode, message); + } else if (data.length == 0) { + throw new InvalidDataException(errorCode, message); + } + } +} diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java new file mode 100644 index 0000000000..68f8bb2019 --- /dev/null +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java @@ -0,0 +1,71 @@ +/* + * + * + * + * + * + * + * + * + */ + +package io.mosip.testrig.apirig.encrypt.util; + +/** + * {@link Enum} for exception constants + * + * @author Urvil Joshi + * @since 1.0.0 + */ +public enum SecurityExceptionCodeConstant { + MOSIP_INVALID_KEY_EXCEPTION("KER-FSE-001", + "invalid Key (key is null or empty or has invalid encoding, wronglength, and uninitialized, etc)."), + MOSIP_INVALID_DATA_LENGTH_EXCEPTION("KER-FSE-002", "check input data length"), + MOSIP_INVALID_DATA_EXCEPTION("KER-FSE-003", "data not valid (currupted,length is not valid etc.)"), + MOSIP_INVALID_ENCRYPTED_DATA_CORRUPT_EXCEPTION("KER-FSE-004", "encrypted data is corrupted"), + MOSIP_INVALID_DATA_SIZE_EXCEPTION("KER-FSE-005", "ecrypted data size is not valid"), + MOSIP_NULL_DATA_EXCEPTION("KER-FSE-006", "data is null or length is 0"), + MOSIP_NULL_METHOD_EXCEPTION("KER-FSE-007", "mosip security method is null"), + MOSIP_NO_SUCH_ALGORITHM_EXCEPTION("KER-FSE-008", "no such algorithm"), + MOSIP_INVALID_PARAM_SPEC_EXCEPTION("KER-FSE-009", "invalid param spec"), + MOSIP_SIGNATURE_EXCEPTION("KER-FSE-010", "invalid signature, maybe null or empty"), + SALT_PROVIDED_IS_NULL_OR_EMPTY("KER-FSE-011", "salt provided is null or empty"); + + /** + * Constant {@link Enum} errorCode + */ + private final String errorCode; + + /** + * Getter for errorMessage + * + * @return get errorMessage value + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * Constant {@link Enum} errorMessage + */ + private final String errorMessage; + + /** + * Constructor for this class + * + * @param value set {@link Enum} value + */ + private SecurityExceptionCodeConstant(final String errorCode, final String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + /** + * Getter for errorCode + * + * @return get errorCode value + */ + public String getErrorCode() { + return errorCode; + } +} diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java index 3c5ccffbb4..e89030f7d9 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java @@ -19,7 +19,7 @@ import io.mosip.kernel.core.util.CryptoUtil; import io.mosip.kernel.core.util.HMACUtils; -import io.mosip.kernel.crypto.jce.util.JWSValidation; +import io.mosip.kernel.crypto.jce.core.CryptoCore; import io.mosip.testrig.apirig.testrunner.JsonPrecondtion; /** @@ -123,8 +123,8 @@ private static String getSignedData(String identityDataBlock) { .generateCertificate(bIS); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(pKey))); - JWSValidation jws = new JWSValidation(); - return jws.jwsSign(identityDataBlock, privateKey, certificate); + CryptoCore cryptoCore = new CryptoCore(); + return cryptoCore.sign(identityDataBlock.getBytes(), privateKey, certificate); } catch (Exception e) { logger.error("Exception Occured in signing the bio data:" + e.getMessage()); return "Automation error occured: "+e.getMessage(); diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java index 1b01bd8de0..4b99a4ddcf 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java @@ -1,6 +1,7 @@ package io.mosip.testrig.apirig.utils; import io.mosip.kernel.core.crypto.spi.CryptoCoreSpec; +import io.mosip.testrig.apirig.encrypt.util.CryptoCore; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -35,7 +36,7 @@ public class CryptoUtil { * {@link CryptoCoreSpec} instance for cryptographic functionalities. */ @Autowired - private CryptoCoreSpec cryptoCore; + private CryptoCore cryptoCore; From b7a872c4b1339d534947440de0d48637cca29f58 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Sat, 21 Sep 2024 14:03:39 +0530 Subject: [PATCH 36/43] MOSIP-35886 - Removing kernel cypto library and added the class in testrig Signed-off-by: Mohanachandran S --- .../io/mosip/testrig/apirig/utils/EncryptionDecrptionUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/EncryptionDecrptionUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/EncryptionDecrptionUtil.java index a235d5fd0f..305d5abb6c 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/EncryptionDecrptionUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/EncryptionDecrptionUtil.java @@ -165,6 +165,7 @@ private EncryptionResponseDto kernelEncrypt(String identityBlock, String refId) SecretKey secretKey = cryptoUtil.genSecKey(); EncryptionResponseDto encryptionResponseDto = new EncryptionResponseDto(); + lOGGER.info("Strated encrypting the Identity block"); byte[] encryptedIdentityBlock = cryptoUtil.symmetricEncrypt(identityBlock.getBytes(StandardCharsets.UTF_8), secretKey); encryptionResponseDto.setEncryptedIdentity(Base64.getUrlEncoder().encodeToString(encryptedIdentityBlock)); From a2608c96b2097e1d25990685fe47654d30bb0b09 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Sat, 21 Sep 2024 18:23:12 +0530 Subject: [PATCH 37/43] MOSIP-35886 - Removing kernel cypto library and added the class in testrig Signed-off-by: Mohanachandran S --- .../mosip/testrig/apirig/admin/fw/config/BeanConfig.java | 4 ++++ .../java/io/mosip/testrig/apirig/utils/BioDataUtility.java | 3 +-- .../mosip/testrig/apirig/utils/BiometricDataUtility.java | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java index 03ce78fc57..8a203a75e1 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java @@ -2,9 +2,13 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import io.mosip.testrig.apirig.encrypt.util.CryptoCore; @Configuration +@Import({CryptoCore.class}) @ComponentScan(basePackages = { "io.mosip.testrig.apirig", "io.mosip.testrig.dslrig"}) public class BeanConfig { int i = 0; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java index 5b46688a8b..4b40c99f26 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java @@ -33,8 +33,7 @@ public class BioDataUtility extends AdminTestUtil { private static final Logger logger = Logger.getLogger(BioDataUtility.class); - @Autowired - private EncryptionDecrptionUtil encryptDecryptUtil; + @Autowired private Encrypt encrypt; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java index e89030f7d9..1e93639053 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java @@ -16,10 +16,11 @@ import javax.ws.rs.core.MediaType; import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import io.mosip.kernel.core.util.CryptoUtil; import io.mosip.kernel.core.util.HMACUtils; -import io.mosip.kernel.crypto.jce.core.CryptoCore; +import io.mosip.testrig.apirig.encrypt.util.CryptoCore; import io.mosip.testrig.apirig.testrunner.JsonPrecondtion; /** @@ -32,6 +33,9 @@ public class BiometricDataUtility extends AuthTestsUtil { + @Autowired + private static CryptoCore cryptoCore; + private static final Logger logger = Logger.getLogger(BiometricDataUtility.class); private static String cryptoEncryptUrl = RunConfigUtil.objRunConfig.getEndPointUrl() @@ -123,7 +127,6 @@ private static String getSignedData(String identityDataBlock) { .generateCertificate(bIS); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(pKey))); - CryptoCore cryptoCore = new CryptoCore(); return cryptoCore.sign(identityDataBlock.getBytes(), privateKey, certificate); } catch (Exception e) { logger.error("Exception Occured in signing the bio data:" + e.getMessage()); From 26ffc152712fc96acc9c463ab335276c0016bf4d Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Sat, 21 Sep 2024 20:52:59 +0530 Subject: [PATCH 38/43] MOSIP-35886 - Removing kernel cypto library and added the class in testrig Signed-off-by: Mohanachandran S --- apitest-commons/pom.xml | 4 +- .../apirig/admin/fw/config/BeanConfig.java | 2 +- .../apirig/encrypt/util/CryptoUtils.java | 58 --------------- .../util/SecurityExceptionCodeConstant.java | 71 ------------------- .../apirig/utils/BiometricDataUtility.java | 1 - .../{encrypt/util => utils}/CryptoCore.java | 2 +- .../testrig/apirig/utils/CryptoUtil.java | 2 +- 7 files changed, 5 insertions(+), 135 deletions(-) delete mode 100644 apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java delete mode 100644 apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java rename apitest-commons/src/main/java/io/mosip/testrig/apirig/{encrypt/util => utils}/CryptoCore.java (99%) diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index 6d6fdf27b3..f1e2ff0395 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -8,7 +8,7 @@ apitest-commons Parent project of MOSIP functional tests https://github.com/mosip/mosip-functional-tests - 1.2.2-SNAPSHOT + 1.2.2-testing-SNAPSHOT @@ -66,7 +66,7 @@ 3.3.9 6.11 1.13 - apitest-commons-1.2.2-SNAPSHOT-jar-with-dependencies + apitest-commons-1.2.2-testing-SNAPSHOT-jar-with-dependencies diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java index 8a203a75e1..e1e7a8ccbd 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java @@ -4,7 +4,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import io.mosip.testrig.apirig.encrypt.util.CryptoCore; +import io.mosip.testrig.apirig.utils.CryptoCore; @Configuration diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java deleted file mode 100644 index 4355774c25..0000000000 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * - * - * - * - * - */ -package io.mosip.testrig.apirig.encrypt.util; - -import io.mosip.kernel.core.crypto.exception.InvalidDataException; -import io.mosip.kernel.core.crypto.exception.NullDataException; -import io.mosip.kernel.crypto.jce.constant.SecurityExceptionCodeConstant; - -/** - * Utility class for crypto - * - * @author Urvil Joshi - * @since 1.0.0 - */ -public class CryptoUtils { - - /** - * Constructor for this class - */ - private CryptoUtils() { - - } - - /** - * Verify if data is null or empty - * - * @param - * - * @param data data provided by user - */ - public static void verifyData(byte[] data) { - if (data == null) { - throw new NullDataException(SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorMessage()); - } else if (data.length == 0) { - throw new InvalidDataException(SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NULL_DATA_EXCEPTION.getErrorMessage()); - } - } - - /** - * Verify if data is null or empty - * - * @param data data provided by user - */ - public static void verifyData(byte[] data, String errorCode, String message) { - if (data == null) { - throw new NullDataException(errorCode, message); - } else if (data.length == 0) { - throw new InvalidDataException(errorCode, message); - } - } -} diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java deleted file mode 100644 index 68f8bb2019..0000000000 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/SecurityExceptionCodeConstant.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * - * - * - * - * - * - * - * - */ - -package io.mosip.testrig.apirig.encrypt.util; - -/** - * {@link Enum} for exception constants - * - * @author Urvil Joshi - * @since 1.0.0 - */ -public enum SecurityExceptionCodeConstant { - MOSIP_INVALID_KEY_EXCEPTION("KER-FSE-001", - "invalid Key (key is null or empty or has invalid encoding, wronglength, and uninitialized, etc)."), - MOSIP_INVALID_DATA_LENGTH_EXCEPTION("KER-FSE-002", "check input data length"), - MOSIP_INVALID_DATA_EXCEPTION("KER-FSE-003", "data not valid (currupted,length is not valid etc.)"), - MOSIP_INVALID_ENCRYPTED_DATA_CORRUPT_EXCEPTION("KER-FSE-004", "encrypted data is corrupted"), - MOSIP_INVALID_DATA_SIZE_EXCEPTION("KER-FSE-005", "ecrypted data size is not valid"), - MOSIP_NULL_DATA_EXCEPTION("KER-FSE-006", "data is null or length is 0"), - MOSIP_NULL_METHOD_EXCEPTION("KER-FSE-007", "mosip security method is null"), - MOSIP_NO_SUCH_ALGORITHM_EXCEPTION("KER-FSE-008", "no such algorithm"), - MOSIP_INVALID_PARAM_SPEC_EXCEPTION("KER-FSE-009", "invalid param spec"), - MOSIP_SIGNATURE_EXCEPTION("KER-FSE-010", "invalid signature, maybe null or empty"), - SALT_PROVIDED_IS_NULL_OR_EMPTY("KER-FSE-011", "salt provided is null or empty"); - - /** - * Constant {@link Enum} errorCode - */ - private final String errorCode; - - /** - * Getter for errorMessage - * - * @return get errorMessage value - */ - public String getErrorMessage() { - return errorMessage; - } - - /** - * Constant {@link Enum} errorMessage - */ - private final String errorMessage; - - /** - * Constructor for this class - * - * @param value set {@link Enum} value - */ - private SecurityExceptionCodeConstant(final String errorCode, final String errorMessage) { - this.errorCode = errorCode; - this.errorMessage = errorMessage; - } - - /** - * Getter for errorCode - * - * @return get errorCode value - */ - public String getErrorCode() { - return errorCode; - } -} diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java index 1e93639053..a4b04bab4a 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BiometricDataUtility.java @@ -20,7 +20,6 @@ import io.mosip.kernel.core.util.CryptoUtil; import io.mosip.kernel.core.util.HMACUtils; -import io.mosip.testrig.apirig.encrypt.util.CryptoCore; import io.mosip.testrig.apirig.testrunner.JsonPrecondtion; /** diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java similarity index 99% rename from apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java rename to apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java index 6e50dc5707..1ff1c7ef97 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/encrypt/util/CryptoCore.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java @@ -1,4 +1,4 @@ -package io.mosip.testrig.apirig.encrypt.util; +package io.mosip.testrig.apirig.utils; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java index 4b99a4ddcf..a382db08cb 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoUtil.java @@ -1,7 +1,7 @@ package io.mosip.testrig.apirig.utils; import io.mosip.kernel.core.crypto.spi.CryptoCoreSpec; -import io.mosip.testrig.apirig.encrypt.util.CryptoCore; + import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; From ae6ffc0f899f7f70ffff406aaa0fb69e2ebac3f5 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Mon, 23 Sep 2024 09:08:05 +0530 Subject: [PATCH 39/43] MOSIP-35886 - Removing kernel cypto library and added the class in testrig Signed-off-by: Mohanachandran S --- apitest-commons/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index f1e2ff0395..6d6fdf27b3 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -8,7 +8,7 @@ apitest-commons Parent project of MOSIP functional tests https://github.com/mosip/mosip-functional-tests - 1.2.2-testing-SNAPSHOT + 1.2.2-SNAPSHOT @@ -66,7 +66,7 @@ 3.3.9 6.11 1.13 - apitest-commons-1.2.2-testing-SNAPSHOT-jar-with-dependencies + apitest-commons-1.2.2-SNAPSHOT-jar-with-dependencies From 2d994dc32fd7d47022f20430171eea04987f13b5 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Mon, 23 Sep 2024 11:25:38 +0530 Subject: [PATCH 40/43] MOSIP-35886 - created the bean for the not injected class Signed-off-by: Mohanachandran S --- .../testrig/apirig/admin/fw/config/BeanConfig.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java index e1e7a8ccbd..59e1ded5e8 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/admin/fw/config/BeanConfig.java @@ -1,10 +1,13 @@ package io.mosip.testrig.apirig.admin.fw.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import io.mosip.testrig.apirig.utils.BioDataUtility; import io.mosip.testrig.apirig.utils.CryptoCore; +import io.mosip.testrig.apirig.utils.EncryptionDecrptionUtil; @Configuration @@ -12,4 +15,15 @@ @ComponentScan(basePackages = { "io.mosip.testrig.apirig", "io.mosip.testrig.dslrig"}) public class BeanConfig { int i = 0; + + @Bean + public BioDataUtility bioDataUtility() { + return new BioDataUtility(); + } + + @Bean + public EncryptionDecrptionUtil encryptionDecrptionUtil() { + return new EncryptionDecrptionUtil(); + } + } From 80aa5445e7dda4e5c8be4d16eab93326680a87d1 Mon Sep 17 00:00:00 2001 From: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:00:01 +0530 Subject: [PATCH 41/43] MOSIP-35955 - Added unique identifier to the DTO Signed-off-by: Mohanachandran S <165888272+mohanachandran-s@users.noreply.github.com> --- .../src/main/java/io/mosip/testrig/apirig/dto/TestCaseDTO.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/dto/TestCaseDTO.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/dto/TestCaseDTO.java index 328ff63e69..830c2ca816 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/dto/TestCaseDTO.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/dto/TestCaseDTO.java @@ -20,4 +20,5 @@ public class TestCaseDTO { private String allowedErrorCodes; private String[] kycFields; private String description; + private String uniqueIdentifier; } From 2fa845d7139711c643c9b3f0c58adf0b014baef9 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 24 Sep 2024 09:23:07 +0530 Subject: [PATCH 42/43] MOSIP-35886 - Modified the injection of Auth Demo Service utils Signed-off-by: Mohanachandran S --- apitest-commons/pom.xml | 4 ++-- .../testrig/apirig/utils/BioDataUtility.java | 4 ++-- .../testrig/apirig/utils/CryptoCore.java | 22 +++++++++---------- .../testrig/apirig/utils/CryptoUtil.java | 4 ++-- .../apirig/utils/EncryptionDecrptionUtil.java | 16 +++++++------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index 6d6fdf27b3..f9a0330341 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -8,7 +8,7 @@ apitest-commons Parent project of MOSIP functional tests https://github.com/mosip/mosip-functional-tests - 1.2.2-SNAPSHOT + 1.2.2-TESTING @@ -66,7 +66,7 @@ 3.3.9 6.11 1.13 - apitest-commons-1.2.2-SNAPSHOT-jar-with-dependencies + apitest-commons-1.2.2-TESTING-jar-with-dependencies diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java index 4b40c99f26..72ede924f4 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/BioDataUtility.java @@ -34,8 +34,8 @@ public class BioDataUtility extends AdminTestUtil { private static final Logger logger = Logger.getLogger(BioDataUtility.class); - @Autowired - private Encrypt encrypt; + //@Autowired + private Encrypt encrypt = new Encrypt(); private String encryptIsoBioValue(String isoBiovalue, String timestamp, String bioValueEncryptionTemplateJson, diff --git a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java index 1ff1c7ef97..855e8a1afe 100644 --- a/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java +++ b/apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/CryptoCore.java @@ -93,14 +93,14 @@ public class CryptoCore implements CryptoCoreSpec getInternalEncryptSessionKeyValue(String jsonString) */ - public EncryptionResponseDto encrypt(String jsonString) throws Exception { + public static EncryptionResponseDto encrypt(String jsonString) throws Exception { String refId= null; boolean isInternal = false; @@ -160,7 +160,7 @@ private static String getRefId(boolean isInternal, boolean isBiometrics) { return refId; } - private EncryptionResponseDto kernelEncrypt(String identityBlock, String refId) throws Exception { + private static EncryptionResponseDto kernelEncrypt(String identityBlock, String refId) throws Exception { // String identityBlock = objMapper.writeValueAsString(jsonString); SecretKey secretKey = cryptoUtil.genSecKey(); EncryptionResponseDto encryptionResponseDto = new EncryptionResponseDto(); From 07ec5f83be42288143d63b471bfb1f540ddf8f88 Mon Sep 17 00:00:00 2001 From: Mohanachandran S Date: Tue, 24 Sep 2024 10:14:49 +0530 Subject: [PATCH 43/43] MOSIP-35886 - Modified the injection of Auth Demo Service utils Signed-off-by: Mohanachandran S --- apitest-commons/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apitest-commons/pom.xml b/apitest-commons/pom.xml index f9a0330341..5766323b56 100644 --- a/apitest-commons/pom.xml +++ b/apitest-commons/pom.xml @@ -8,7 +8,7 @@ apitest-commons Parent project of MOSIP functional tests https://github.com/mosip/mosip-functional-tests - 1.2.2-TESTING + 1.2.1-java21-SNAPSHOT @@ -66,7 +66,7 @@ 3.3.9 6.11 1.13 - apitest-commons-1.2.2-TESTING-jar-with-dependencies + apitest-commons-1.2.1-java21-SNAPSHOT-jar-with-dependencies