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