From 5fee30b8c072e35d2cdcc388b2d3b77c435ce9cb Mon Sep 17 00:00:00 2001 From: Karthikeyan Rajendran <70887864+karthik-tarento@users.noreply.github.com> Date: Thu, 28 Apr 2022 15:53:04 +0530 Subject: [PATCH] SB-29489 | extended user profile feature (#1064) --- controller/conf/routes | 1 + .../resources/cassandratablecolumn.properties | 3 +- .../org/sunbird/exception/ResponseCode.java | 5 +- .../sunbird/exception/ResponseMessage.java | 4 + .../main/java/org/sunbird/keys/JsonKey.java | 5 + .../sunbird/operations/ActorOperations.java | 1 + service/pom.xml | 5 + .../org/sunbird/actor/role/UserRoleActor.java | 59 +++ .../sunbird/actor/user/UserUpdateActor.java | 24 ++ .../java/org/sunbird/model/user/User.java | 17 + .../user/ExtendedUserProfileService.java | 11 + .../impl/ExtendedUserProfileServiceImpl.java | 45 +++ .../service/user/impl/UserServiceImpl.java | 5 + .../org/sunbird/util/user/ProfileUtil.java | 49 +++ .../UserExtendedProfileSchemaValidator.java | 76 ++++ .../actor/role/UserRoleActorTestV2.java | 220 +++++++++++ .../actor/search/SearchHandlerActorTest.java | 2 +- .../user/UserManagementActorTestBase.java | 2 + .../actor/user/UserUpdateActorTest.java | 108 ++++++ .../user/UserExtendedProfileReadTest.java | 350 ++++++++++++++++++ .../ExtendedUserProfileServiceImplTest.java | 4 + ...serExtendedProfileSchemaValidatorTest.java | 109 ++++++ 22 files changed, 1102 insertions(+), 3 deletions(-) create mode 100644 service/src/main/java/org/sunbird/service/user/ExtendedUserProfileService.java create mode 100644 service/src/main/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImpl.java create mode 100644 service/src/main/java/org/sunbird/util/user/ProfileUtil.java create mode 100644 service/src/main/java/org/sunbird/util/user/UserExtendedProfileSchemaValidator.java create mode 100644 service/src/test/java/org/sunbird/actor/role/UserRoleActorTestV2.java create mode 100644 service/src/test/java/org/sunbird/service/user/UserExtendedProfileReadTest.java create mode 100644 service/src/test/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImplTest.java create mode 100644 service/src/test/java/org/sunbird/util/user/UserExtendedProfileSchemaValidatorTest.java diff --git a/controller/conf/routes b/controller/conf/routes index 1e63e405b..637d734b5 100644 --- a/controller/conf/routes +++ b/controller/conf/routes @@ -19,6 +19,7 @@ POST /v2/user/signup @controllers.usermanagement.User PATCH /v1/user/update @controllers.usermanagement.UserController.updateUser(request: play.mvc.Http.Request) PATCH /v2/user/update @controllers.usermanagement.UserController.updateUserV2(request: play.mvc.Http.Request) PATCH /v3/user/update @controllers.usermanagement.UserController.updateUserV3(request: play.mvc.Http.Request) +PATCH /private/user/v1/update @controllers.usermanagement.UserController.updateUserV3(request: play.mvc.Http.Request) GET /v1/user/read/:uid @controllers.usermanagement.UserController.getUserByIdV3(uid:String, request: play.mvc.Http.Request) GET /v2/user/read/:uid @controllers.usermanagement.UserController.getUserByIdV3(uid:String, request: play.mvc.Http.Request) diff --git a/core/cassandra-utils/src/main/resources/cassandratablecolumn.properties b/core/cassandra-utils/src/main/resources/cassandratablecolumn.properties index 0b0f31562..3aa9b546e 100644 --- a/core/cassandra-utils/src/main/resources/cassandratablecolumn.properties +++ b/core/cassandra-utils/src/main/resources/cassandratablecolumn.properties @@ -156,4 +156,5 @@ attemptedcount=attemptedCount hashtagid=hashTagId associationtype=associationType profileusertypes=profileUserTypes -organisationsubtype=organisationSubType \ No newline at end of file +organisationsubtype=organisationSubType +profiledetails=profileDetails diff --git a/core/platform-common/src/main/java/org/sunbird/exception/ResponseCode.java b/core/platform-common/src/main/java/org/sunbird/exception/ResponseCode.java index eb1f24371..13ea01585 100644 --- a/core/platform-common/src/main/java/org/sunbird/exception/ResponseCode.java +++ b/core/platform-common/src/main/java/org/sunbird/exception/ResponseCode.java @@ -174,7 +174,10 @@ public enum ResponseCode { TOO_MANY_REQUESTS(429), SERVICE_UNAVAILABLE(503), PARTIAL_SUCCESS_RESPONSE(206), - IM_A_TEAPOT(418); + IM_A_TEAPOT(418), + extendUserProfileNotLoaded(ResponseMessage.Key.EXTENDED_USER_PROFILE_NOT_LOADED, + ResponseMessage.Message.EXTENDED_USER_PROFILE_NOT_LOADED), + roleProcessingInvalidOrgError(ResponseMessage.Key.ROLE_PROCESSING_INVALID_ORG, ResponseMessage.Message.ROLE_PROCESSING_INVALID_ORG); private int responseCode; /** error code contains String value */ private String errorCode; diff --git a/core/platform-common/src/main/java/org/sunbird/exception/ResponseMessage.java b/core/platform-common/src/main/java/org/sunbird/exception/ResponseMessage.java index 9afa5e722..d14698abd 100644 --- a/core/platform-common/src/main/java/org/sunbird/exception/ResponseMessage.java +++ b/core/platform-common/src/main/java/org/sunbird/exception/ResponseMessage.java @@ -108,6 +108,8 @@ public interface ResponseMessage { String INVALID_CONSENT_STATUS = "Consent status is invalid"; String USER_TYPE_CONFIG_IS_EMPTY = "userType config is empty for the statecode {0}"; String SERVER_ERROR = "server error"; + String EXTENDED_USER_PROFILE_NOT_LOADED = "Failed to load extendedProfileSchemaConfig from System_Settings table"; + String ROLE_PROCESSING_INVALID_ORG = "Error while processing assign role. Invalid Organisation Id"; } interface Key { @@ -184,5 +186,7 @@ public interface ResponseMessage { String INVALID_OBJECT_TYPE = "0072"; String INACTIVE_USER = "0073"; String INVALID_CSV_FILE = "0074"; + String EXTENDED_USER_PROFILE_NOT_LOADED = "0075"; + String ROLE_PROCESSING_INVALID_ORG = "0076"; } } diff --git a/core/platform-common/src/main/java/org/sunbird/keys/JsonKey.java b/core/platform-common/src/main/java/org/sunbird/keys/JsonKey.java index 59f24e0be..309ad4df2 100644 --- a/core/platform-common/src/main/java/org/sunbird/keys/JsonKey.java +++ b/core/platform-common/src/main/java/org/sunbird/keys/JsonKey.java @@ -628,6 +628,11 @@ public final class JsonKey { public static final String SOFT_DELETE_PREVIOUS_ORG = "softDeleteOldOrg"; public static final String FORCE_MIGRATION = "forceMigration"; public static final String NOTIFY_USER_MIGRATION = "notifyMigration"; + public static final String PROFILE_DETAILS = "profileDetails"; + public static final String EXTENDED_PROFILE_SCHEMA_CONFIG = "extendedProfileSchemaConfig"; + public static final String MANDATORY_FIELDS_EXISTS = "mandatoryFieldsExists"; + public static final String OSID = "osid"; + public static final String DISABLE_MULTIPLE_ORG_ROLE = "sunbird_disable_multiple_org_role"; private JsonKey() {} } diff --git a/core/platform-common/src/main/java/org/sunbird/operations/ActorOperations.java b/core/platform-common/src/main/java/org/sunbird/operations/ActorOperations.java index 565661abd..1633d043a 100644 --- a/core/platform-common/src/main/java/org/sunbird/operations/ActorOperations.java +++ b/core/platform-common/src/main/java/org/sunbird/operations/ActorOperations.java @@ -147,6 +147,7 @@ public enum ActorOperations { UPSERT_LOCATION_TO_ES("upsertLocationDataToES", "LBKGUPSRT"), DELETE_LOCATION_FROM_ES("deleteLocationDataFromES", "LBKGDEL"), USER_CURRENT_LOGIN("userCurrentLogin", "USRLOG"); + private String value; private String operationCode; diff --git a/service/pom.xml b/service/pom.xml index e5e4df117..f0bed7b09 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -118,6 +118,11 @@ <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> + <dependency> + <groupId>org.everit.json</groupId> + <artifactId>org.everit.json.schema</artifactId> + <version>1.5.1</version> + </dependency> </dependencies> <build> <sourceDirectory>${basedir}/src/main/java</sourceDirectory> diff --git a/service/src/main/java/org/sunbird/actor/role/UserRoleActor.java b/service/src/main/java/org/sunbird/actor/role/UserRoleActor.java index 409dc0b84..07534532a 100644 --- a/service/src/main/java/org/sunbird/actor/role/UserRoleActor.java +++ b/service/src/main/java/org/sunbird/actor/role/UserRoleActor.java @@ -1,11 +1,19 @@ package org.sunbird.actor.role; import akka.actor.ActorRef; + +import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.sunbird.actor.user.UserBaseActor; +import org.sunbird.exception.ProjectCommonException; +import org.sunbird.exception.ResponseCode; import org.sunbird.keys.JsonKey; import org.sunbird.operations.ActorOperations; import org.sunbird.request.Request; @@ -16,6 +24,7 @@ import org.sunbird.service.user.UserRoleService; import org.sunbird.service.user.impl.UserRoleServiceImpl; import org.sunbird.telemetry.dto.TelemetryEnvKey; import org.sunbird.util.DataCacheHandler; +import org.sunbird.util.PropertiesCache; import org.sunbird.util.Util; public class UserRoleActor extends UserBaseActor { @@ -65,6 +74,11 @@ public class UserRoleActor extends UserBaseActor { requestMap.put(JsonKey.ROLE_OPERATION, "assignRole"); List<String> roles = (List<String>) requestMap.get(JsonKey.ROLES); RoleService.validateRoles(roles); + String configValue = PropertiesCache.getInstance().getProperty(JsonKey.DISABLE_MULTIPLE_ORG_ROLE); + if(Boolean.parseBoolean(configValue)) { + validateRequest(userRoleService.getUserRoles((String) requestMap.get(JsonKey.USER_ID), actorMessage.getRequestContext()), + (String) requestMap.get(JsonKey.ORGANISATION_ID), actorMessage.getRequestContext()); + } userRolesList = userRoleService.updateUserRole(requestMap, actorMessage.getRequestContext()); } else { List<Map<String, Object>> roleList = @@ -77,6 +91,25 @@ public class UserRoleActor extends UserBaseActor { response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); sender().tell(response, self()); + userRolesList = userRoleService.getUserRoles((String) requestMap.get(JsonKey.USER_ID), null, actorMessage.getRequestContext()); + ObjectMapper mapper = new ObjectMapper(); + userRolesList + .stream() + .forEach( + userRole -> { + try { + String dbScope = (String) userRole.get(JsonKey.SCOPE); + if (StringUtils.isNotBlank(dbScope)) { + List<Map<String, String>> scope = mapper.readValue(dbScope, ArrayList.class); + userRole.put(JsonKey.SCOPE, scope); + } + } catch (Exception e) { + logger.error( + actorMessage.getRequestContext(), + "Exception because of mapper read value" + userRole.get(JsonKey.SCOPE), + e); + } + }); syncUserRoles( JsonKey.USER, (String) requestMap.get(JsonKey.USER_ID), @@ -107,4 +140,30 @@ public class UserRoleActor extends UserBaseActor { ex); } } + + private void validateRequest(List<Map<String, Object>> userRolesList, String organisationId, RequestContext context) { + userRolesList + .stream() + .forEach( + userRole -> { + try { + List<Map<String, String>> dbScope = (List<Map<String, String>>) userRole.get(JsonKey.SCOPE); + if (CollectionUtils.isNotEmpty(dbScope)) { + for(Map<String, String> orgScope : dbScope) { + if(StringUtils.isNotBlank(orgScope.get(JsonKey.ORGANISATION_ID)) && !orgScope.get(JsonKey.ORGANISATION_ID).equalsIgnoreCase(organisationId)) { + logger.info(context, "UserRoleActor: Given OrganisationId is different than existing one."); + throw new ProjectCommonException( + ResponseCode.roleProcessingInvalidOrgError, + ResponseCode.roleProcessingInvalidOrgError.getErrorMessage(), + ResponseCode.SERVER_ERROR.getResponseCode()); + } + } + } + } catch(ProjectCommonException pce) { + throw pce; + } catch (Exception e) { + logger.error( context, "Exception because of mapper read value" + userRole.get(JsonKey.SCOPE), e); + } + }); + } } diff --git a/service/src/main/java/org/sunbird/actor/user/UserUpdateActor.java b/service/src/main/java/org/sunbird/actor/user/UserUpdateActor.java index 2c80cdd10..b86927e98 100644 --- a/service/src/main/java/org/sunbird/actor/user/UserUpdateActor.java +++ b/service/src/main/java/org/sunbird/actor/user/UserUpdateActor.java @@ -34,10 +34,13 @@ import org.sunbird.response.Response; import org.sunbird.service.organisation.OrgService; import org.sunbird.service.organisation.impl.OrgServiceImpl; import org.sunbird.service.user.AssociationMechanism; +import org.sunbird.service.user.ExtendedUserProfileService; import org.sunbird.service.user.UserService; +import org.sunbird.service.user.impl.ExtendedUserProfileServiceImpl; import org.sunbird.service.user.impl.UserServiceImpl; import org.sunbird.telemetry.dto.TelemetryEnvKey; import org.sunbird.util.*; +import org.sunbird.util.user.ProfileUtil; import org.sunbird.util.user.UserUtil; public class UserUpdateActor extends UserBaseActor { @@ -48,6 +51,7 @@ public class UserUpdateActor extends UserBaseActor { private final OrgService orgService = OrgServiceImpl.getInstance(); private final UserSelfDeclarationDao userSelfDeclarationDao = UserSelfDeclarationDaoImpl.getInstance(); + private ExtendedUserProfileService userProfileService = ExtendedUserProfileServiceImpl.getInstance(); @Inject @Named("user_profile_update_actor") @@ -86,6 +90,10 @@ public class UserUpdateActor extends UserBaseActor { Map<String, Object> userMap = actorMessage.getRequest(); logger.info(actorMessage.getRequestContext(), "Incoming update request body: " + userMap); userRequestValidator.validateUpdateUserRequest(actorMessage); + if(null != actorMessage.getRequest().get(JsonKey.PROFILE_DETAILS)) { + userProfileService.validateProfile(actorMessage); + convertProfileObjToString(actorMessage); + } // update externalIds provider from channel to orgId UserUtil.updateExternalIdsProviderWithOrgId(userMap, actorMessage.getRequestContext()); Map<String, Object> userDbRecord = @@ -659,4 +667,20 @@ public class UserUpdateActor extends UserBaseActor { backgroundJobManager.tell(userRequest, self()); } } + + private void convertProfileObjToString(Request actorMessage) { + //ProfileObject is available - add 'osid' and then convert it to String. + try { + Map profileObject = (Map) actorMessage.getRequest().get(JsonKey.PROFILE_DETAILS); + ProfileUtil.appendIdToReferenceObjects(profileObject); + String profileStr = mapper.writeValueAsString(profileObject); + actorMessage.getRequest().put(JsonKey.PROFILE_DETAILS, profileStr); + } catch(Exception e) { + throw new ProjectCommonException( + ResponseCode.invalidValue, + ProjectUtil.formatMessage( + ResponseCode.invalidValue.getErrorMessage(), JsonKey.PROFILE_DETAILS), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + } } diff --git a/service/src/main/java/org/sunbird/model/user/User.java b/service/src/main/java/org/sunbird/model/user/User.java index 7ac81ff7e..1447dbebc 100644 --- a/service/src/main/java/org/sunbird/model/user/User.java +++ b/service/src/main/java/org/sunbird/model/user/User.java @@ -63,6 +63,7 @@ public class User implements Serializable { private String profileUserType; private String profileLocation; private String profileUserTypes; + private String profileDetails; public Map<String, String> getAllTncAccepted() { return allTncAccepted; @@ -436,4 +437,20 @@ public class User implements Serializable { this.profileLocation = (String) profileLocation; } } + + public String getProfileDetails() { + return profileDetails; + } + + public void setProfileDetails(Object profileDetails) { + if(profileDetails != null && !(profileDetails instanceof String)) { + try { + this.profileDetails = new ObjectMapper().writeValueAsString(profileDetails); + } catch (Exception e) { + this.profileDetails = ""; + } + } else { + this.profileDetails = (String) profileDetails; + } + } } diff --git a/service/src/main/java/org/sunbird/service/user/ExtendedUserProfileService.java b/service/src/main/java/org/sunbird/service/user/ExtendedUserProfileService.java new file mode 100644 index 000000000..7d983d9a8 --- /dev/null +++ b/service/src/main/java/org/sunbird/service/user/ExtendedUserProfileService.java @@ -0,0 +1,11 @@ +package org.sunbird.service.user; + +import org.sunbird.request.Request; + +public interface ExtendedUserProfileService { + /** + * Validate json payload of user profile of a given user request + * @param userRequest + */ + public void validateProfile(Request userRequest); +} diff --git a/service/src/main/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImpl.java b/service/src/main/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImpl.java new file mode 100644 index 000000000..0e8289396 --- /dev/null +++ b/service/src/main/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImpl.java @@ -0,0 +1,45 @@ +package org.sunbird.service.user.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.json.JSONObject; +import org.sunbird.exception.ProjectCommonException; +import org.sunbird.exception.ResponseCode; +import org.sunbird.keys.JsonKey; +import org.sunbird.logging.LoggerUtil; +import org.sunbird.request.Request; +import org.sunbird.service.user.ExtendedUserProfileService; +import org.sunbird.util.user.UserExtendedProfileSchemaValidator; + +import java.util.Map; + +public class ExtendedUserProfileServiceImpl implements ExtendedUserProfileService { + private static final String SCHEMA = "profileDetails.json"; + private static final ObjectMapper mapper = new ObjectMapper(); + private static ExtendedUserProfileServiceImpl instance = null; + private LoggerUtil logger = new LoggerUtil(UserExtendedProfileSchemaValidator.class); + + public static ExtendedUserProfileService getInstance() { + if(instance == null) { + instance = new ExtendedUserProfileServiceImpl(); + } + return instance; + } + + @Override + public void validateProfile(Request userRequest) { + if (userRequest!=null && userRequest.get(JsonKey.PROFILE_DETAILS)!=null) { + try{ + String userProfile = mapper.writeValueAsString(userRequest.getRequest().get(JsonKey.PROFILE_DETAILS)); + JSONObject obj = new JSONObject(userProfile); + UserExtendedProfileSchemaValidator.validate(SCHEMA, obj); + ((Map)userRequest.getRequest().get(JsonKey.PROFILE_DETAILS)).put(JsonKey.MANDATORY_FIELDS_EXISTS, obj.get(JsonKey.MANDATORY_FIELDS_EXISTS)); + } catch (Exception e){ + logger.error(userRequest.getRequestContext(), "ExtendedUserProfileServiceImpl.validateProfile :: failed to validate profile.", e); + throw new ProjectCommonException( + ResponseCode.extendUserProfileNotLoaded, + ResponseCode.extendUserProfileNotLoaded.getErrorMessage(), + ResponseCode.extendUserProfileNotLoaded.getResponseCode()); + } + } + } +} diff --git a/service/src/main/java/org/sunbird/service/user/impl/UserServiceImpl.java b/service/src/main/java/org/sunbird/service/user/impl/UserServiceImpl.java index 5e769a1ec..fa02afb5e 100644 --- a/service/src/main/java/org/sunbird/service/user/impl/UserServiceImpl.java +++ b/service/src/main/java/org/sunbird/service/user/impl/UserServiceImpl.java @@ -36,6 +36,7 @@ import org.sunbird.service.user.UserOrgService; import org.sunbird.service.user.UserRoleService; import org.sunbird.service.user.UserService; import org.sunbird.util.*; +import org.sunbird.util.user.ProfileUtil; import org.sunbird.util.user.UserTncUtil; import org.sunbird.util.user.UserUtil; import scala.concurrent.Await; @@ -90,6 +91,10 @@ public class UserServiceImpl implements UserService { ResponseCode.resourceNotFound, MessageFormat.format(ResponseCode.resourceNotFound.getErrorMessage(), JsonKey.USER)); } + if (user.get(JsonKey.PROFILE_DETAILS) != null) { + logger.debug(context, "getUserDetailsById :: read Profile details String is :: " + user.get(JsonKey.PROFILE_DETAILS).toString()); + user.put(JsonKey.PROFILE_DETAILS, ProfileUtil.toMap(user.get(JsonKey.PROFILE_DETAILS).toString())); + } user.putAll(Util.getUserDefaultValue()); return user; } diff --git a/service/src/main/java/org/sunbird/util/user/ProfileUtil.java b/service/src/main/java/org/sunbird/util/user/ProfileUtil.java new file mode 100644 index 000000000..ec80fc8da --- /dev/null +++ b/service/src/main/java/org/sunbird/util/user/ProfileUtil.java @@ -0,0 +1,49 @@ +package org.sunbird.util.user; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.sunbird.keys.JsonKey; +import org.sunbird.logging.LoggerUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class ProfileUtil { + public static final ObjectMapper mapper = new ObjectMapper(); + private static final LoggerUtil logger = new LoggerUtil(ProfileUtil.class); + + public static Map<String,Object> toMap(String jsonString) { + try { + TypeReference<HashMap<String, Object>> typeRef + = new TypeReference<HashMap<String, Object>>() {}; + return mapper.readValue(jsonString, typeRef); + } catch (Exception e) { + logger.error( "ProfileUtil Exception " , e); + } + return null; + } + + public static void appendIdToReferenceObjects(Map<String, Object> profile) { + for (Map.Entry<String, Object> entry : profile.entrySet()) { + if (entry.getValue() instanceof ArrayList) { + List<Object> valueList = (List<Object>) entry.getValue(); + for(Object entryObj : valueList) { + if (entryObj instanceof HashMap) { + Map<String, Object> entryMap = (Map<String, Object>) entryObj; + if (entryMap.get(JsonKey.OSID) == null) { + entryMap.put(JsonKey.OSID, UUID.randomUUID().toString()); + } + } + } + } else if (entry.getValue() instanceof HashMap) { + appendIdToReferenceObjects((Map<String, Object>) entry.getValue()); + } + } + if(profile.get(JsonKey.OSID) == null) { + profile.put(JsonKey.OSID, UUID.randomUUID().toString()); + } + } +} diff --git a/service/src/main/java/org/sunbird/util/user/UserExtendedProfileSchemaValidator.java b/service/src/main/java/org/sunbird/util/user/UserExtendedProfileSchemaValidator.java new file mode 100644 index 000000000..dbf8012bf --- /dev/null +++ b/service/src/main/java/org/sunbird/util/user/UserExtendedProfileSchemaValidator.java @@ -0,0 +1,76 @@ +package org.sunbird.util.user; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.everit.json.schema.Schema; +import org.everit.json.schema.ValidationException; +import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONObject; +import org.sunbird.exception.ProjectCommonException; +import org.sunbird.exception.ResponseCode; +import org.sunbird.keys.JsonKey; +import org.sunbird.logging.LoggerUtil; +import org.sunbird.util.DataCacheHandler; + +import java.util.HashMap; +import java.util.Map; + +public class UserExtendedProfileSchemaValidator { + private static final String PRIMARY_EMAIL_FIELD = "primaryEmail"; + + private static LoggerUtil logger = new LoggerUtil(UserExtendedProfileSchemaValidator.class); + private static Map<String, String> schemas = new HashMap<>(); + + public static void loadSchemas() { + ObjectMapper mapper = new ObjectMapper(); + try { + String schemaConfig = DataCacheHandler.getConfigSettings().get(JsonKey.EXTENDED_PROFILE_SCHEMA_CONFIG); + TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {}; + Map<String, Object> schemaMap = mapper.readValue(schemaConfig, typeRef); + for (Map.Entry entry : schemaMap.entrySet()) { + schemas.put(entry.getKey().toString(), mapper.writeValueAsString(entry.getValue())); + } + } catch (Exception e) { + logger.error("UserExtendedProfileSchemaValidator.loadSchemas :: failed to load schemas.", e); + throw new ProjectCommonException( + ResponseCode.extendUserProfileNotLoaded, + ResponseCode.extendUserProfileNotLoaded.getErrorMessage(), + ResponseCode.extendUserProfileNotLoaded.getResponseCode()); + } + logger.info(null, String.format("schemas size :- " + schemas.size())); + } + + public static boolean validate(String entityType, JSONObject payload) throws Exception { + Schema schema = getEntitySchema(entityType); + try { + schema.validate(payload); + payload.put(JsonKey.MANDATORY_FIELDS_EXISTS, Boolean.TRUE); + } catch (ValidationException e) { + if (e.getAllMessages().toString().contains(PRIMARY_EMAIL_FIELD)) { + throw new Exception(e.getAllMessages().toString()); + } else { + logger.error("Mandatory attributes are not present", e); + payload.put(JsonKey.MANDATORY_FIELDS_EXISTS, Boolean.FALSE); + } + } + return true; + } + + private static Schema getEntitySchema(String entityType) throws Exception { + Schema schema; + try { + if(schemas.isEmpty()) { + loadSchemas(); + } + String definitionContent = schemas.get(entityType); + JSONObject rawSchema = new JSONObject(definitionContent); + SchemaLoader schemaLoader = SchemaLoader.builder() + .schemaJson(rawSchema).build(); + schema = schemaLoader.load().build(); + } catch (Exception ioe) { + logger.error("UserExtendedProfileSchemaValidator.getEntitySchema :: failed to validate entityType : " + entityType, ioe); + throw new Exception("can't validate, " + entityType + ": schema has a problem!"); + } + return schema; + } +} diff --git a/service/src/test/java/org/sunbird/actor/role/UserRoleActorTestV2.java b/service/src/test/java/org/sunbird/actor/role/UserRoleActorTestV2.java new file mode 100644 index 000000000..27af0df48 --- /dev/null +++ b/service/src/test/java/org/sunbird/actor/role/UserRoleActorTestV2.java @@ -0,0 +1,220 @@ +package org.sunbird.actor.role; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.pattern.Patterns; +import akka.pattern.PipeToSupport; +import akka.testkit.javadsl.TestKit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.sunbird.cassandraimpl.CassandraOperationImpl; +import org.sunbird.common.ElasticSearchRestHighImpl; +import org.sunbird.common.factory.EsClientFactory; +import org.sunbird.dao.role.impl.RoleDaoImpl; +import org.sunbird.dao.user.UserOrgDao; +import org.sunbird.dao.user.impl.UserOrgDaoImpl; +import org.sunbird.datasecurity.DecryptionService; +import org.sunbird.exception.ProjectCommonException; +import org.sunbird.exception.ResponseCode; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.keys.JsonKey; +import org.sunbird.operations.ActorOperations; +import org.sunbird.request.Request; +import org.sunbird.response.Response; +import org.sunbird.service.organisation.OrgService; +import org.sunbird.service.organisation.impl.OrgServiceImpl; +import org.sunbird.service.role.RoleService; +import org.sunbird.util.DataCacheHandler; +import org.sunbird.util.PropertiesCache; +import org.sunbird.util.Util; + +import java.util.*; + +import static akka.testkit.JavaTestKit.duration; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +@PrepareForTest({ + RoleService.class, + DataCacheHandler.class, + PropertiesCache.class, + ServiceFactory.class, + RoleDaoImpl.class, + EsClientFactory.class, + ElasticSearchRestHighImpl.class, + Util.class, + UserOrgDaoImpl.class, + DecryptionService.class, + Patterns.class, + PipeToSupport.PipeableFuture.class, + OrgServiceImpl.class, + OrgService.class +}) + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({ + "javax.management.*", + "javax.net.ssl.*", + "javax.security.*", + "jdk.internal.reflect.*", + "javax.crypto.*" +}) +public class UserRoleActorTestV2 { + private ActorSystem system = ActorSystem.create("system"); + private static final Props props = Props.create(UserRoleActor.class); + private CassandraOperationImpl cassandraOperation; + private ElasticSearchRestHighImpl esService; + private PropertiesCache propertiesCache; + + @Before + public void beforeEachTest() { + PowerMockito.mockStatic(DataCacheHandler.class); + PowerMockito.mockStatic(ServiceFactory.class); + PowerMockito.mockStatic(RoleDaoImpl.class); + PowerMockito.mockStatic(Util.class); + PowerMockito.mockStatic(UserOrgDaoImpl.class); + PowerMockito.mockStatic(EsClientFactory.class); + propertiesCache = PowerMockito.mock(PropertiesCache.class); + PowerMockito.mockStatic(PropertiesCache.class); + + + Map<String, Object> roleMap = new HashMap<>(); + roleMap.put("anyRole4", "anyRole4"); + roleMap.put("EDITOR","Editor"); + roleMap.put("PUBLIC","Public"); + PowerMockito.when(DataCacheHandler.getRoleMap()).thenReturn(roleMap); + PowerMockito.when(PropertiesCache.getInstance()).thenReturn(propertiesCache); + + cassandraOperation = mock(CassandraOperationImpl.class); + RoleDaoImpl roleDao = Mockito.mock(RoleDaoImpl.class); + when(RoleDaoImpl.getInstance()).thenReturn(roleDao); + UserOrgDao userOrgDao = Mockito.mock(UserOrgDaoImpl.class); + when(UserOrgDaoImpl.getInstance()).thenReturn(userOrgDao); + when(userOrgDao.updateUserOrg(Mockito.anyObject(), Mockito.any())) + .thenReturn(getSuccessResponse()); + when(ServiceFactory.getInstance()).thenReturn(cassandraOperation); + when(cassandraOperation.getAllRecords(Mockito.anyString(), Mockito.anyString(), Mockito.any())) + .thenReturn(getCassandraResponse()); + when(cassandraOperation.getRecordsByCompositeKey( + Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.any())) + .thenReturn(getRecordByPropertyResponse()); + when(cassandraOperation.batchInsert( + Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any())) + .thenReturn(getSuccessResponse()); + when(cassandraOperation.updateRecord( + Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(getSuccessResponse()); + cassandraOperation.deleteRecord( + Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.any()); + when(cassandraOperation.getRecordById( + Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.any())) + .thenReturn(getCassandraUserRoleResponse()); + esService = mock(ElasticSearchRestHighImpl.class); + when(EsClientFactory.getInstance(Mockito.anyString())).thenReturn(esService); + + } + + @Test + public void testAssignRoleFailure() { + PowerMockito.when(propertiesCache.getProperty(JsonKey.DISABLE_MULTIPLE_ORG_ROLE)).thenReturn("true"); + Map<String, Object> req = getAssignRoleRequest(); + Request request = getRequest(req, ActorOperations.ASSIGN_ROLES.getValue()); + boolean result = testScenario(request, ResponseCode.roleProcessingInvalidOrgError); + assertTrue(result); + } + + private boolean testScenario(Request request, ResponseCode errorCode) { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + subject.tell(request, probe.getRef()); + + if (errorCode == null) { + Response res = probe.expectMsgClass(duration("100 second"), Response.class); + return null != res && res.getResponseCode() == ResponseCode.OK; + } else { + ProjectCommonException res = + probe.expectMsgClass(duration("100 second"), ProjectCommonException.class); + return res.getResponseCode().name().equals(errorCode.name()) + || res.getErrorResponseCode() == errorCode.getResponseCode(); + } + } + + private Map<String, Object> getAssignRoleRequest() { + Map<String, Object> map = new HashMap<>(); + map.put(JsonKey.ORGANISATION_ID, "ORGANISATION_ID"); + map.put(JsonKey.USER_ID, "USER_ID"); + map.put(JsonKey.ROLES, Arrays.asList("anyRole4")); + return map; + } + + private Request getRequest(Map<String, Object> requestData, String actorOperation) { + Request reqObj = new Request(); + reqObj.setRequest(requestData); + reqObj.setOperation(actorOperation); + return reqObj; + } + + private Response getSuccessResponse() { + Response response = new Response(); + response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); + return response; + } + + private Response getCassandraResponse() { + Response response = new Response(); + List<Map<String, Object>> list = new ArrayList<>(); + Map<String, Object> orgMap = new HashMap<>(); + orgMap.put(JsonKey.ID, "ORGANISATION_ID"); + list.add(orgMap); + response.put(JsonKey.RESPONSE, list); + return response; + } + + private Response getRecordByPropertyResponse() { + Response response = new Response(); + List<Map<String, Object>> list = new ArrayList<>(); + Map<String, Object> orgMap = new HashMap<>(); + orgMap.put(JsonKey.ID, "ORGANISATION_ID"); + orgMap.put(JsonKey.IS_DELETED, false); + list.add(orgMap); + response.put(JsonKey.RESPONSE, list); + return response; + } + + private Response getCassandraUserRoleResponse() { + Response response = new Response(); + List<Map<String, Object>> list = new ArrayList<>(); + Map<String, Object> orgMap = new HashMap<>(); + orgMap.put(JsonKey.ID, "ORGANISATION_ID"); + orgMap.put(JsonKey.USER_ID, "USER_ID"); + orgMap.put(JsonKey.ROLE, "anyRole1"); + orgMap.put( + JsonKey.SCOPE, + "[{\"organisationId\":\"ORGANISATION_ID1\"},{\"organisationId\":\"ORGANISATION_ID\"}]"); + list.add(orgMap); + orgMap = new HashMap<>(); + orgMap.put(JsonKey.ID, "ORGANISATION_ID"); + orgMap.put(JsonKey.USER_ID, "USER_ID"); + orgMap.put(JsonKey.ROLE, "anyRole2"); + orgMap.put(JsonKey.SCOPE, "[{\"organisationId\":\"ORGANISATION_ID\"}]"); + list.add(orgMap); + orgMap = new HashMap<>(); + orgMap.put(JsonKey.ID, "ORGANISATION_ID"); + orgMap.put(JsonKey.USER_ID, "USER_ID"); + orgMap.put(JsonKey.ROLE, "anyRole3"); + orgMap.put( + JsonKey.SCOPE, + "[{\"organisationId\":\"ORGANISATION_ID1\"},{\"organisationId\":\"ORGANISATION_ID\"}]"); + list.add(orgMap); + response.put(JsonKey.RESPONSE, list); + return response; + } +} diff --git a/service/src/test/java/org/sunbird/actor/search/SearchHandlerActorTest.java b/service/src/test/java/org/sunbird/actor/search/SearchHandlerActorTest.java index bc94d3396..e5adcc146 100644 --- a/service/src/test/java/org/sunbird/actor/search/SearchHandlerActorTest.java +++ b/service/src/test/java/org/sunbird/actor/search/SearchHandlerActorTest.java @@ -44,7 +44,7 @@ import scala.concurrent.Promise; "jdk.internal.reflect.*", "javax.crypto.*" }) -@Ignore + public class SearchHandlerActorTest { private static ActorSystem system; diff --git a/service/src/test/java/org/sunbird/actor/user/UserManagementActorTestBase.java b/service/src/test/java/org/sunbird/actor/user/UserManagementActorTestBase.java index d224e7fe5..fcd9cb2ea 100644 --- a/service/src/test/java/org/sunbird/actor/user/UserManagementActorTestBase.java +++ b/service/src/test/java/org/sunbird/actor/user/UserManagementActorTestBase.java @@ -15,6 +15,8 @@ import akka.pattern.PipeToSupport; import akka.testkit.javadsl.TestKit; import akka.util.Timeout; import java.util.*; + +import org.codehaus.jackson.map.ObjectMapper; import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mockito; diff --git a/service/src/test/java/org/sunbird/actor/user/UserUpdateActorTest.java b/service/src/test/java/org/sunbird/actor/user/UserUpdateActorTest.java index 05766744d..42ec02a27 100644 --- a/service/src/test/java/org/sunbird/actor/user/UserUpdateActorTest.java +++ b/service/src/test/java/org/sunbird/actor/user/UserUpdateActorTest.java @@ -1,6 +1,8 @@ package org.sunbird.actor.user; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.when; import akka.actor.ActorRef; @@ -19,19 +21,28 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; import org.sunbird.exception.ResponseCode; import org.sunbird.keys.JsonKey; import org.sunbird.model.location.Location; import org.sunbird.model.organisation.Organisation; import org.sunbird.operations.ActorOperations; import org.sunbird.request.Request; +import org.sunbird.service.user.ExtendedUserProfileService; +import org.sunbird.service.user.impl.ExtendedUserProfileServiceImpl; import org.sunbird.util.DataCacheHandler; import org.sunbird.util.user.UserUtil; import scala.concurrent.Future; +@PrepareForTest({ + ExtendedUserProfileServiceImpl.class, + ExtendedUserProfileService.class +}) public class UserUpdateActorTest extends UserManagementActorTestBase { public final Props props = Props.create(UserUpdateActor.class); + private String extendedProfileConfig = + "{\"profileDetails.json\":{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"type\":\"object\",\"definitions\":{\"@type\":{\"type\":\"string\"},\"MaritalStatus\":{\"type\":\"string\",\"enum\":[\"Married\",\"Single\"]},\"NonEmptyStr\":{\"type\":\"string\",\"pattern\":\"\\\\A(?!\\\\s*\\\\Z).+\"}},\"properties\":{\"personalDetails\":{\"type\":\"object\",\"title\":\"The Personal Details Schema\",\"required\":[\"firstname\",\"surname\",\"maritalStatus\"],\"properties\":{\"firstname\":{\"$id\":\"#/properties/firstname\",\"$ref\":\"#/definitions/NonEmptyStr\",\"$comment\":\"User first name\"},\"surname\":{\"$id\":\"#/properties/surname\",\"$ref\":\"#/definitions/NonEmptyStr\",\"$comment\":\"User surname\"},\"maritalStatus\":{\"$id\":\"#/properties/maritalStatus\",\"$ref\":\"#/definitions/MaritalStatus\",\"$comment\":\"Marital Status\"}}}},\"title\":\"profileDetails\",\"required\":[\"personalDetails\"]}}"; @Before public void before() { @@ -50,6 +61,7 @@ public class UserUpdateActorTest extends UserManagementActorTestBase { Map<String, String> configMap = new HashMap<>(); configMap.put(JsonKey.CUSTODIAN_ORG_CHANNEL, "channel"); configMap.put(JsonKey.CUSTODIAN_ORG_ID, "custodianRootOrgId"); + configMap.put("extendedProfileSchemaConfig", extendedProfileConfig); when(DataCacheHandler.getConfigSettings()).thenReturn(configMap); Map<String, Map<String, List<String>>> userTypeConfigMap = new HashMap<>(); @@ -489,4 +501,100 @@ public class UserUpdateActorTest extends UserManagementActorTestBase { boolean result = testScenario(request, ResponseCode.mandatoryParamsMissing, props); assertTrue(result); } + + @Test + public void testUpdateExtendedUserProfileV3() { + PowerMockito.mockStatic(ExtendedUserProfileServiceImpl.class); + ExtendedUserProfileService extendedUserProfileService = mock(ExtendedUserProfileService.class); + when(ExtendedUserProfileServiceImpl.getInstance()).thenReturn(extendedUserProfileService); + + Map<String, Object> req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); + req.put(JsonKey.USER_ID, "userId"); + Map<String, Object> profileDetails = new HashMap<>(); + Map<String, Object> personalDetails = new HashMap<>(); + personalDetails.put("firstname","Demo Name"); + personalDetails.put("surname", "Surname"); + personalDetails.put("maritalStatus","Married"); + profileDetails.put("personalDetails", personalDetails); + req.put(JsonKey.PROFILE_DETAILS, profileDetails); + Map<String, Object> user = new HashMap<>(); + user.put(JsonKey.PHONE, "4346345377"); + user.put(JsonKey.EMAIL, "username@gmail.com"); + user.put(JsonKey.USERNAME, "username"); + user.put(JsonKey.ROOT_ORG_ID, "rootOrgId"); + when(UserUtil.isEmailOrPhoneDiff(Mockito.anyMap(), Mockito.anyMap(), Mockito.anyString())) + .thenReturn(true); + when(UserUtil.validateExternalIdsAndReturnActiveUser(Mockito.anyMap(), Mockito.any())) + .thenReturn(user); + Request request = getRequest(true, true, true, req, ActorOperations.UPDATE_USER_V3); + boolean result = testScenario(request, null, props); + assertTrue(result); + } + + @Test + public void testUpdateExtendedUserProfileFailureV3() { + //Remove schemaConfig from DataCacheHandler + Map<String, String> configMap = new HashMap<>(); + configMap.put(JsonKey.CUSTODIAN_ORG_CHANNEL, "channel"); + configMap.put(JsonKey.CUSTODIAN_ORG_ID, "custodianRootOrgId"); + when(DataCacheHandler.getConfigSettings()).thenReturn(configMap); + + Map<String, Object> req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); + req.put(JsonKey.USER_ID, "userId"); + Map<String, Object> profileDetails = new HashMap<>(); + Map<String, Object> personalDetails = new HashMap<>(); + personalDetails.put("martialStatus","Married"); + profileDetails.put("unknownObject", personalDetails); + req.put(JsonKey.PROFILE_DETAILS, profileDetails); + Map<String, Object> user = new HashMap<>(); + user.put(JsonKey.PHONE, "4346345377"); + user.put(JsonKey.EMAIL, "username@gmail.com"); + user.put(JsonKey.USERNAME, "username"); + user.put(JsonKey.ROOT_ORG_ID, "rootOrgId"); + when(UserUtil.isEmailOrPhoneDiff(Mockito.anyMap(), Mockito.anyMap(), Mockito.anyString())) + .thenReturn(true); + when(UserUtil.validateExternalIdsAndReturnActiveUser(Mockito.anyMap(), Mockito.any())) + .thenReturn(user); + Request request = getRequest(true, true, true, req, ActorOperations.UPDATE_USER_V3); + boolean result = testScenario(request, ResponseCode.extendUserProfileNotLoaded, props); + assertTrue(result); + } + + @Test + public void testUpdateInvalidExtendedUserProfileFailureV3() { + PowerMockito.mockStatic(ExtendedUserProfileServiceImpl.class); + ExtendedUserProfileService extendedUserProfileService = mock(ExtendedUserProfileService.class); + when(ExtendedUserProfileServiceImpl.getInstance()).thenReturn(extendedUserProfileService); + + Map<String, Object> req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); + req.put(JsonKey.USER_ID, "userId"); + req.put(JsonKey.PROFILE_DETAILS, "this is my profile"); + Map<String, Object> user = new HashMap<>(); + user.put(JsonKey.PHONE, "4346345377"); + user.put(JsonKey.EMAIL, "username@gmail.com"); + user.put(JsonKey.USERNAME, "username"); + user.put(JsonKey.ROOT_ORG_ID, "rootOrgId"); + when(UserUtil.isEmailOrPhoneDiff(Mockito.anyMap(), Mockito.anyMap(), Mockito.anyString())) + .thenReturn(true); + when(UserUtil.validateExternalIdsAndReturnActiveUser(Mockito.anyMap(), Mockito.any())) + .thenReturn(user); + Request request = getRequest(true, true, true, req, ActorOperations.UPDATE_USER_V3); + boolean result = testScenario(request, ResponseCode.invalidValue, props); + assertTrue(result); + } + + @Test + public void testUpdateUserProfileWithInvalidOperation() { + Map<String, Object> req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); + req.put(JsonKey.USER_ID, "userId"); + Map<String, Object> user = new HashMap<>(); + user.put(JsonKey.PHONE, "4346345377"); + Request request = getRequest(true, true, true, req, ActorOperations.CREATE_USER); + boolean result = testScenario(request, ResponseCode.extendUserProfileNotLoaded, props); + assertFalse(result); + } } diff --git a/service/src/test/java/org/sunbird/service/user/UserExtendedProfileReadTest.java b/service/src/test/java/org/sunbird/service/user/UserExtendedProfileReadTest.java new file mode 100644 index 000000000..963593f19 --- /dev/null +++ b/service/src/test/java/org/sunbird/service/user/UserExtendedProfileReadTest.java @@ -0,0 +1,350 @@ +package org.sunbird.service.user; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.sunbird.actor.organisation.validator.OrgTypeValidator; +import org.sunbird.cassandra.CassandraOperation; +import org.sunbird.cassandraimpl.CassandraOperationImpl; +import org.sunbird.common.ElasticSearchHelper; +import org.sunbird.common.ElasticSearchRestHighImpl; +import org.sunbird.common.factory.EsClientFactory; +import org.sunbird.dao.user.UserDao; +import org.sunbird.dao.user.UserOrgDao; +import org.sunbird.dao.user.UserRoleDao; +import org.sunbird.dao.user.impl.UserDaoImpl; +import org.sunbird.dao.user.impl.UserOrgDaoImpl; +import org.sunbird.dao.user.impl.UserRoleDaoImpl; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.keys.JsonKey; +import org.sunbird.model.user.User; +import org.sunbird.operations.ActorOperations; +import org.sunbird.request.Request; +import org.sunbird.response.Response; +import org.sunbird.util.DataCacheHandler; +import org.sunbird.util.UserUtility; +import org.sunbird.util.Util; +import org.sunbird.util.user.UserUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.nullable; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ + UserUtil.class, + ServiceFactory.class, + CassandraOperationImpl.class, + DataCacheHandler.class, + UserDao.class, + UserDaoImpl.class, + UserOrgDao.class, + UserOrgDaoImpl.class, + UserUtility.class, + Util.class, + ElasticSearchRestHighImpl.class, + EsClientFactory.class, + ElasticSearchHelper.class, + UserRoleDao.class, + UserRoleDaoImpl.class, + OrgTypeValidator.class +}) +@PowerMockIgnore({ + "javax.management.*", + "javax.net.ssl.*", + "javax.security.*", + "jdk.internal.reflect.*", + "javax.crypto.*" +}) +public class UserExtendedProfileReadTest { + private String extendedProfileConfig = + "{\"profileDetails.json\":{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"type\":\"object\",\"definitions\":{\"@type\":{\"type\":\"string\"},\"MaritalStatus\":{\"type\":\"string\",\"enum\":[\"Married\",\"Single\"]},\"NonEmptyStr\":{\"type\":\"string\",\"pattern\":\"\\\\A(?!\\\\s*\\\\Z).+\"}},\"properties\":{\"personalDetails\":{\"type\":\"object\",\"title\":\"The Personal Details Schema\",\"required\":[\"firstname\",\"surname\",\"maritalStatus\"],\"properties\":{\"firstname\":{\"$id\":\"#/properties/firstname\",\"$ref\":\"#/definitions/NonEmptyStr\",\"$comment\":\"User first name\"},\"surname\":{\"$id\":\"#/properties/surname\",\"$ref\":\"#/definitions/NonEmptyStr\",\"$comment\":\"User surname\"},\"maritalStatus\":{\"$id\":\"#/properties/maritalStatus\",\"$ref\":\"#/definitions/MaritalStatus\",\"$comment\":\"Marital Status\"}}}},\"title\":\"profileDetails\",\"required\":[\"personalDetails\"]}}"; + + @Before + public void beforeEachTest() { + PowerMockito.mockStatic(DataCacheHandler.class); + Map<String, String> config = new HashMap<>(); + config.put("extendedProfileSchemaConfig", extendedProfileConfig); + when(DataCacheHandler.getConfigSettings()).thenReturn(config); + OrgTypeValidator orgTypeValidator = mock(OrgTypeValidator.class); + PowerMockito.mockStatic(OrgTypeValidator.class); + when(OrgTypeValidator.getInstance()).thenReturn(orgTypeValidator); + when(orgTypeValidator.getValueByType(JsonKey.ORG_TYPE_SCHOOL)).thenReturn(2); + when(orgTypeValidator.isOrgTypeExist("board")).thenReturn(true); + when(orgTypeValidator.isOrgTypeExist("school")).thenReturn(true); + } + + @Test + public void getUserExtendedProfileDataTest() throws JsonProcessingException { + PowerMockito.mockStatic(ServiceFactory.class); + CassandraOperation cassandraOperationImpl = mock(CassandraOperation.class); + when(ServiceFactory.getInstance()).thenReturn(cassandraOperationImpl); + Response response = new Response(); + List<Map<String, Object>> resp = new ArrayList<>(); + Map<String, Object> resMap = new HashMap<>(); + resMap.put(JsonKey.USER_ID, "1234"); + resMap.put(JsonKey.IS_DELETED, false); + resp.add(resMap); + response.put(JsonKey.RESPONSE, resp); + when(cassandraOperationImpl.getRecordById( + Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any())) + .thenReturn(response); + + Response response2 = new Response(); + List<Map<String, Object>> resp2 = new ArrayList<>(); + Map<String, Object> userList2 = new HashMap<>(); + userList2.put(JsonKey.USER_ID, "1234"); + userList2.put(JsonKey.ORG_NAME, "rootOrg"); + userList2.put(JsonKey.IS_DELETED, false); + userList2.put(JsonKey.ORGANISATION_ID, "4578963210"); + List<String> roles = new ArrayList<>(); + roles.add("PUBLIC"); + roles.add("ORG_ADMIN"); + userList2.put(JsonKey.ROLES, roles); + userList2.put("dob", "1992-11-24"); + + Map<String, Object> userList3 = new HashMap<>(); + userList3.put(JsonKey.USER_ID, "1234"); + userList3.put(JsonKey.ORG_NAME, "subOrg"); + userList3.put(JsonKey.IS_DELETED, false); + userList3.put(JsonKey.ORGANISATION_ID, "457896321012"); + userList3.put(JsonKey.ROLES, roles); + userList3.put("dob", "1992-11-24"); + + resp2.add(userList2); + resp2.add(userList3); + response2.put(JsonKey.RESPONSE, resp2); + when(cassandraOperationImpl.getRecordById( + Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.any())) + .thenReturn(response2); + + UserDao userDao = PowerMockito.mock(UserDao.class); + PowerMockito.mockStatic(UserDaoImpl.class); + Mockito.when(UserDaoImpl.getInstance()).thenReturn(userDao); + PowerMockito.mockStatic(UserUtility.class); + PowerMockito.mockStatic(Util.class); + Mockito.when(UserUtility.decryptUserData(Mockito.anyMap())) + .thenReturn(getUserDbMap("1234567890")); + Map<String, Object> userDetails = getValidUserResponse("1234567890"); + // Add wrong profile userType for test + userDetails.put(JsonKey.PROFILE_USERTYPE, "{\"useType\":45}"); + String profileLocation = "[{\"id\":\"4567891231\",\"type\":\"state\"}]"; + userDetails.put(JsonKey.PROFILE_LOCATION, profileLocation); + Mockito.when(userDao.getUserDetailsById(Mockito.anyString(), Mockito.any())) + .thenReturn(userDetails); + + UserOrgDao userOrgDao = PowerMockito.mock(UserOrgDao.class); + PowerMockito.mockStatic(UserOrgDaoImpl.class); + Mockito.when(UserOrgDaoImpl.getInstance()).thenReturn(userOrgDao); + Mockito.when(userOrgDao.getUserOrgListByUserId(Mockito.anyString(), Mockito.any())) + .thenReturn(response2); + + Map<String, Object> org = new HashMap<>(); + org.put(JsonKey.ID, "4578963210"); + org.put(JsonKey.ORGANISATION_ID, "4578963210"); + org.put(JsonKey.LOCATION_ID, "987542312459"); + org.put(JsonKey.ORG_NAME, "org name"); + org.put(JsonKey.HASHTAGID, "4578963210"); + org.put(JsonKey.CHANNEL, "channel"); + org.put(JsonKey.ORGANISATION_TYPE, 2); + List<String> locIds = new ArrayList<>(); + locIds.add("location1"); + locIds.add("location2"); + org.put(JsonKey.LOCATION_IDS, locIds); + List<Map<String, Object>> orgList = new ArrayList<>(); + orgList.add(org); + Response orgRes = new Response(); + orgRes.getResult().put(JsonKey.RESPONSE, orgList); + + Map<String, Object> locn = new HashMap<>(); + locn.put(JsonKey.ID, "location1"); + locn.put(JsonKey.CODE, "code1"); + locn.put(JsonKey.NAME, "locn 1"); + locn.put(JsonKey.TYPE, "state"); + locn.put(JsonKey.PARENT_ID, null); + + Map<String, Object> locn2 = new HashMap<>(); + locn2.put(JsonKey.ID, "location2"); + locn2.put(JsonKey.CODE, "code2"); + locn2.put(JsonKey.NAME, "locn 2"); + locn2.put(JsonKey.TYPE, "district"); + locn2.put(JsonKey.PARENT_ID, "location1"); + + Map<String, Object> block = new HashMap<>(); + block.put(JsonKey.ID, "blockId"); + block.put(JsonKey.CODE, "block1"); + block.put(JsonKey.NAME, "block1"); + block.put(JsonKey.TYPE, "block"); + block.put(JsonKey.PARENT_ID, "location2"); + + Map<String, Object> cluster = new HashMap<>(); + cluster.put(JsonKey.ID, "clusterId"); + cluster.put(JsonKey.CODE, "cluster1"); + cluster.put(JsonKey.NAME, "cluster1"); + cluster.put(JsonKey.TYPE, "cluster"); + cluster.put(JsonKey.PARENT_ID, "blockId"); + + Map<String, Object> school = new HashMap<>(); + school.put(JsonKey.ID, "schoolId"); + school.put(JsonKey.CODE, "school1"); + school.put(JsonKey.NAME, "school1"); + school.put(JsonKey.TYPE, "school"); + school.put(JsonKey.PARENT_ID, "clusterId"); + + List<Map<String, Object>> locnList = new ArrayList<>(); + locnList.add(locn); + locnList.add(locn2); + locnList.add(block); + locnList.add(cluster); + locnList.add(school); + Response locnResponse = new Response(); + locnResponse.getResult().put(JsonKey.RESPONSE, locnList); + + Mockito.when( + cassandraOperationImpl.getPropertiesValueById( + Mockito.anyString(), + Mockito.anyString(), + Mockito.anyList(), + Mockito.anyList(), + Mockito.any())) + .thenReturn(orgRes) + .thenReturn(orgRes) + .thenReturn(orgRes) + .thenReturn(orgRes) + .thenReturn(locnResponse) + .thenReturn(locnResponse); + + UserProfileReadService userProfileReadService = new UserProfileReadService(); + + List<Map<String, String>> externalIds = new ArrayList<>(); + Map<String, String> externalId = new HashMap<>(); + externalId.put(JsonKey.ID, "extid"); + externalId.put(JsonKey.ID_TYPE, "4578963210"); + externalId.put(JsonKey.PROVIDER, "4578963210"); + externalId.put(JsonKey.ORIGINAL_EXTERNAL_ID, "extid1"); + externalId.put(JsonKey.ORIGINAL_ID_TYPE, "4578963210"); + externalId.put(JsonKey.ORIGINAL_PROVIDER, "4578963210"); + externalIds.add(externalId); + + Map<String, String> externalId1 = new HashMap<>(); + externalId1.put(JsonKey.ID, "extid1@test.com"); + externalId1.put(JsonKey.ID_TYPE, "DECLARED_EMAIL"); + externalId1.put(JsonKey.PROVIDER, "4578963210"); + externalId1.put(JsonKey.ORIGINAL_EXTERNAL_ID, "extid1@test.com"); + externalId1.put(JsonKey.ORIGINAL_ID_TYPE, "DECLARED_EMAIL"); + externalId1.put(JsonKey.ORIGINAL_PROVIDER, "4578963210"); + externalIds.add(externalId1); + + Map<String, String> externalId2 = new HashMap<>(); + externalId2.put(JsonKey.ID, "district"); + externalId2.put(JsonKey.ID_TYPE, "DECLARED_DISTRICT"); + externalId2.put(JsonKey.PROVIDER, "4578963210"); + externalId2.put(JsonKey.ORIGINAL_EXTERNAL_ID, "district"); + externalId2.put(JsonKey.ORIGINAL_ID_TYPE, "DECLARED_DISTRICT"); + externalId2.put(JsonKey.ORIGINAL_PROVIDER, "4578963210"); + externalIds.add(externalId2); + + PowerMockito.mockStatic(UserUtil.class); + when(UserUtil.getExternalIds(Mockito.anyString(), Mockito.anyBoolean(), Mockito.any())) + .thenReturn(externalIds); + + PowerMockito.mockStatic(UserRoleDaoImpl.class); + UserRoleDao userRoleDao = PowerMockito.mock(UserRoleDao.class); + Mockito.when(UserRoleDaoImpl.getInstance()).thenReturn(userRoleDao); + List<Map<String, Object>> userRoleDetails = new ArrayList<>(); + Map<String, Object> userRoleMap = new HashMap<>(); + userRoleMap.put("role", "CONTENT_CREATOR"); + userRoleMap.put("userid", "4a3ded8a-d731-4f58-a722-e63b00925cd0"); + userRoleMap.put("scope", "[{\"orgId\":\"4578963210\"}]"); + userRoleDetails.add(userRoleMap); + Map<String, Object> userRoleMap1 = new HashMap<>(); + userRoleMap1.put("role", "COURSE_CREATOR"); + userRoleMap1.put("userid", "4a3ded8a-d731-4f58-a722-e63b00925cd0"); + userRoleMap1.put("scope", "[{\"orgId\":\"4578963210\"}]"); + userRoleDetails.add(userRoleMap1); + Mockito.when( + userRoleDao.getUserRoles(Mockito.anyString(), nullable(String.class), Mockito.any())) + .thenReturn(userRoleDetails); + Response response1 = + userProfileReadService.getUserProfileData(getProfileReadRequest("1234567890")); + Assert.assertNotNull(response1); + } + + private Request getProfileReadRequest(String userId) { + Request reqObj = new Request(); + Map<String, Object> innerMap = new HashMap<>(); + innerMap.put(JsonKey.REQUESTED_BY, "1234567890"); + innerMap.put(JsonKey.PRIVATE, false); + innerMap.put(JsonKey.FIELDS, "topic,organisations,roles,locations,declarations,externalIds"); + Map<String, Object> reqMap = getUserProfileRequest(userId); + reqObj.setRequest(reqMap); + reqObj.setContext(innerMap); + reqObj.setOperation(ActorOperations.GET_USER_PROFILE_V3.getValue()); + return reqObj; + } + + private Map<String, Object> getUserProfileRequest(String userId) { + Map<String, Object> reqMap = new HashMap<>(); + reqMap.put(JsonKey.USER_ID, userId); + reqMap.put(JsonKey.ROOT_ORG_ID, "validRootOrgId"); + return reqMap; + } + + private Map<String, Object> getValidUserResponse(String userid) throws JsonProcessingException { + User user = new User(); + user.setId(userid); + user.setEmail("anyEmail@gmail.com"); + user.setChannel("channel"); + user.setPhone("9876543210"); + user.setRootOrgId("4578963210"); + user.setMaskedEmail("any****@gmail.com"); + user.setMaskedPhone("987*****0"); + user.setIsDeleted(false); + user.setFlagsValue(3); + user.setUserId(userid); + user.setFirstName("Demo Name"); + user.setUserName("validUserName"); + Map<String, Object> profileDetails = new HashMap<>(); + Map<String, Object> personalDetails = new HashMap<>(); + personalDetails.put("firstname","Demo Name"); + personalDetails.put("surname", "Surname"); + personalDetails.put("maritalStatus","Married"); + profileDetails.put("personalDetails", personalDetails); + user.setProfileDetails(profileDetails); + + ObjectMapper mapper = new ObjectMapper(); + Map<String, Object> result = mapper.convertValue(user, Map.class); + return result; + } + + private Map<String, Object> getUserDbMap(String userid) throws JsonProcessingException { + Map<String, Object> userDbMap = new HashMap<>(); + userDbMap.put(JsonKey.USERNAME, "validUserName"); + userDbMap.put(JsonKey.CHANNEL, "channel"); + userDbMap.put(JsonKey.EMAIL, "anyEmail@gmail.com"); + userDbMap.put(JsonKey.ROOT_ORG_ID, "4578963210"); + userDbMap.put(JsonKey.PHONE, "9876543210"); + userDbMap.put(JsonKey.FLAGS_VALUE, 3); + userDbMap.put(JsonKey.USER_TYPE, "TEACHER"); + userDbMap.put(JsonKey.MASKED_PHONE, "987*****0"); + userDbMap.put(JsonKey.USER_ID, userid); + userDbMap.put(JsonKey.ID, userid); + userDbMap.put(JsonKey.FIRST_NAME, "Demo Name"); + userDbMap.put(JsonKey.IS_DELETED, false); + userDbMap.put("profileDetails","{\"personalDetails\":{\"firstname\":\"Demo Name\",\"surname\":\"Surname\",\"maritalStatus\":\"Married\"}}"); + return userDbMap; + } +} diff --git a/service/src/test/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImplTest.java b/service/src/test/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImplTest.java new file mode 100644 index 000000000..e9c2903c2 --- /dev/null +++ b/service/src/test/java/org/sunbird/service/user/impl/ExtendedUserProfileServiceImplTest.java @@ -0,0 +1,4 @@ +package org.sunbird.service.user.impl; + +public class ExtendedUserProfileServiceImplTest { +} diff --git a/service/src/test/java/org/sunbird/util/user/UserExtendedProfileSchemaValidatorTest.java b/service/src/test/java/org/sunbird/util/user/UserExtendedProfileSchemaValidatorTest.java new file mode 100644 index 000000000..250d09387 --- /dev/null +++ b/service/src/test/java/org/sunbird/util/user/UserExtendedProfileSchemaValidatorTest.java @@ -0,0 +1,109 @@ +package org.sunbird.util.user; + +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.when; + +import com.google.gson.JsonObject; +import org.jclouds.json.Json; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.sunbird.exception.ProjectCommonException; +import org.sunbird.exception.ResponseCode; +import org.sunbird.keys.JsonKey; +import org.sunbird.util.DataCacheHandler; + +import java.util.HashMap; +import java.util.Map; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({ + "javax.management.*", + "javax.net.ssl.*", + "javax.security.*", + "jdk.internal.reflect.*", + "javax.crypto.*" +}) +@PrepareForTest({ + DataCacheHandler.class +}) +public class UserExtendedProfileSchemaValidatorTest { + + @Before + public void beforeEachTest() { + PowerMockito.mockStatic(DataCacheHandler.class); + String extendedProfileConfig = + "{\"profileDetails.json\":{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"type\":\"object\",\"definitions\":{\"@type\":{\"type\":\"string\"},\"MaritalStatus\":{\"type\":\"string\",\"enum\":[\"Married\",\"Single\"]},\"NonEmptyStr\":{\"type\":\"string\",\"pattern\":\"\\\\A(?!\\\\s*\\\\Z).+\"}},\"properties\":{\"personalDetails\":{\"type\":\"object\",\"title\":\"The Personal Details Schema\",\"required\":[\"firstname\",\"surname\",\"maritalStatus\"],\"properties\":{\"firstname\":{\"$id\":\"#/properties/firstname\",\"$ref\":\"#/definitions/NonEmptyStr\",\"$comment\":\"User first name\"},\"surname\":{\"$id\":\"#/properties/surname\",\"$ref\":\"#/definitions/NonEmptyStr\",\"$comment\":\"User surname\"},\"maritalStatus\":{\"$id\":\"#/properties/maritalStatus\",\"$ref\":\"#/definitions/MaritalStatus\",\"$comment\":\"Marital Status\"}}}},\"title\":\"profileDetails\",\"required\":[\"personalDetails\"]}}"; + + Map<String, String> configMap = new HashMap<>(); + configMap.put(JsonKey.EXTENDED_PROFILE_SCHEMA_CONFIG, extendedProfileConfig); + when(DataCacheHandler.getConfigSettings()).thenReturn(configMap); + UserExtendedProfileSchemaValidator.loadSchemas(); + } + + @Test + public void loadSchemaTestSuccessful() { + UserExtendedProfileSchemaValidator.loadSchemas(); + } + + @Test + public void loadSchemaTestFailure() { + String extendedProfileConfig = "[{\"key\":\"value\"}]"; + + Map<String, String> configMap = new HashMap<>(); + configMap.put(JsonKey.EXTENDED_PROFILE_SCHEMA_CONFIG, extendedProfileConfig); + when(DataCacheHandler.getConfigSettings()).thenReturn(configMap); + try { + UserExtendedProfileSchemaValidator.loadSchemas(); + } catch(ProjectCommonException pe) { + assertTrue(pe.getResponseCode() == ResponseCode.extendUserProfileNotLoaded); + } + } + + @Test + public void validateSchema() { + JSONObject profileObject = new JSONObject(); + JSONObject personalDetails = new JSONObject(); + personalDetails.put("firstname", "firstname"); + personalDetails.put("surname", "surname"); + personalDetails.put("maritalStatus", "Single"); + profileObject.put("personalDetails", personalDetails); + + try { + assertTrue(UserExtendedProfileSchemaValidator.validate("profileDetails.json", profileObject)); + } catch(Exception e) { + assertTrue("Failed to validate schema.", false); + } + } + + @Test + public void validateSchemaWithInvalidObject() { + try { + UserExtendedProfileSchemaValidator.validate("profileDetails.json", null); + } catch(Exception e) { + assertTrue("Received Exception when parsing object..", true); + } + } + + @Test + public void validateSchemaWithInvalidType() { + JSONObject profileObject = new JSONObject(); + JSONObject personalDetails = new JSONObject(); + personalDetails.put("firstname", "firstname"); + personalDetails.put("surname", "surname"); + personalDetails.put("maritalStatus", "Single"); + profileObject.put("personalDetails", personalDetails); + + try { + UserExtendedProfileSchemaValidator.validate("myProfile", profileObject); + } catch(Exception e) { + assertTrue("Failed to validate schema.", true); + } + } + +} -- GitLab