From 2dd227b859f9c2ff0fdb6bbeba54dd9fddc90f65 Mon Sep 17 00:00:00 2001 From: Bharathwaj <32808916+BharathwajShankar@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:47:24 +0530 Subject: [PATCH] Issue #LR-676 merge: User Delete - ownership transfer api (#1236) --- .../usermanagement/UserController.java | 8 + controller/app/util/ACTORS.java | 3 +- controller/conf/application.conf | 10 + controller/conf/routes | 2 +- .../org/sunbird/exception/ResponseCode.java | 3 + .../sunbird/exception/ResponseMessage.java | 2 + .../main/java/org/sunbird/keys/JsonKey.java | 8 + .../sunbird/operations/ActorOperations.java | 3 +- .../resources/externalresource.properties | 2 +- .../user/UserOwnershipTransferActor.java | 226 ++++++++++++ .../user/UserOwnershipTransferActorTest.java | 328 ++++++++++++++++++ .../resources/externalresource.properties | 3 +- 12 files changed, 593 insertions(+), 5 deletions(-) create mode 100644 service/src/main/java/org/sunbird/actor/user/UserOwnershipTransferActor.java create mode 100644 service/src/test/java/org/sunbird/actor/user/UserOwnershipTransferActorTest.java diff --git a/controller/app/controllers/usermanagement/UserController.java b/controller/app/controllers/usermanagement/UserController.java index 5a0455363d..605e4360aa 100644 --- a/controller/app/controllers/usermanagement/UserController.java +++ b/controller/app/controllers/usermanagement/UserController.java @@ -55,6 +55,9 @@ public class UserController extends BaseController { @Inject @Named("user_self_declaration_management_actor") private ActorRef userSelfDeclarationManagementActor; + @Inject + @Named("user_ownership_transfer_actor") + private ActorRef userOwnershipTransferActor; public CompletionStage createUser(Http.Request httpRequest) { return handleRequest( @@ -472,4 +475,9 @@ public CompletionStage updateUserDeclarations(Http.Request httpRequest) true, httpRequest); } + public CompletionStage ownershipTransferUser(Http.Request httpRequest) { + return handleRequest(userOwnershipTransferActor, + ActorOperations.USER_OWNERSHIP_TRANSFER.getValue(), + httpRequest.body().asJson(), httpRequest); + } } diff --git a/controller/app/util/ACTORS.java b/controller/app/util/ACTORS.java index 8bc03332e9..07cd94a52b 100644 --- a/controller/app/util/ACTORS.java +++ b/controller/app/util/ACTORS.java @@ -103,7 +103,8 @@ public enum ACTORS { USER_UPDATE_ACTOR(UserUpdateActor.class, "user_update_actor"), BACKGROUND_JOB_MANAGER_ACTOR(BackgroundJobManager.class, "background_job_manager_actor"), USER_DELETION_BACKGROUND_JOB_ACTOR( - UserDeletionBackgroundJobActor.class, "user_deletion_background_job_actor"); + UserDeletionBackgroundJobActor.class, "user_deletion_background_job_actor"), + USER_OWNERSHIP_TRANSFER_ACTOR(UserOwnershipTransferActor.class,"user_ownership_transfer_actor"); ACTORS(Class clazz, String name) { actorClass = clazz; diff --git a/controller/conf/application.conf b/controller/conf/application.conf index f037c48f53..5597be4d19 100644 --- a/controller/conf/application.conf +++ b/controller/conf/application.conf @@ -115,6 +115,16 @@ akka { { dispatcher = akka.actor.brr-usr-dispatcher } + "/user_ownership_transfer_actor" + { + router = smallest-mailbox-pool + nr-of-instances = 5 + dispatcher = brr-usr-dispatcher + } + "/user_ownership_transfer_actor/*" + { + dispatcher = akka.actor.brr-usr-dispatcher + } "/background_job_manager_actor" { router = smallest-mailbox-pool diff --git a/controller/conf/routes b/controller/conf/routes index 6bd2fb233f..acf54cc3fd 100644 --- a/controller/conf/routes +++ b/controller/conf/routes @@ -140,4 +140,4 @@ POST /v1/system/settings/set @controllers.systemsettings.Syst POST /v1/user/delete @controllers.usermanagement.UserStatusController.deleteUser(request: play.mvc.Http.Request) - +POST /v1/user/ownership/transfer @controllers.usermanagement.UserController.ownershipTransferUser(request: play.mvc.Http.Request) 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 e99a3e0db4..7242cc357c 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 @@ -180,6 +180,9 @@ public enum ResponseCode { ResponseMessage.Message.INVALID_TENANT_SECURITY_LEVEL_LOWER), cannotDeleteUser( ResponseMessage.Key.CANNOT_DELETE_USER, ResponseMessage.Message.CANNOT_DELETE_USER), + + cannotTransferOwnership( + ResponseMessage.Key.CANNOT_TRANSFER_OWNERSHIP, ResponseMessage.Message.CANNOT_TRANSFER_OWNERSHIP), OK(200), SUCCESS(200), CLIENT_ERROR(400), 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 827a90a32b..4fb12567c4 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 @@ -123,6 +123,7 @@ interface Message { String INVALID_TENANT_SECURITY_LEVEL_LOWER = "Tenant level's security {0} cannot be lower than system level's security {1}. Please provide a valid data security level."; String CANNOT_DELETE_USER = "User is restricted from deleting account based on roles!"; + String CANNOT_TRANSFER_OWNERSHIP = "User is restricted from transfering the ownership based on roles!"; } interface Key { @@ -208,5 +209,6 @@ interface Key { String MISSING_DEFAULT_SECURITY_LEVEL = "0081"; String INVALID_TENANT_SECURITY_LEVEL_LOWER = "0082"; String CANNOT_DELETE_USER = "0083"; + String CANNOT_TRANSFER_OWNERSHIP = "0084"; } } 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 f5244557cb..f62344b73c 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 @@ -671,6 +671,14 @@ public final class JsonKey { public static final String USER_TABLE_STATUS = "userTable"; public static final String SUGGESTED_USERS = "suggested_users"; public static final String DELETE_USER_ACTON = "delete-user"; + public static final String ACTION_BY = "actionBy"; + public static final String FROM_USER = "fromUser"; + public static final String TO_USER = "toUser"; + public static final String USER_OWNERSHIP_TRANSFER_ACTION = "ownership-transfer"; + public static final String FROM_USER_PROFILE = "fromUserProfile"; + public static final String TO_USER_PROFILE = "toUserProfile"; + public static final String ASSET_INFORMATION = "assetInformation"; + public static final String USER_OWNERSHIP_TRANSFER_TOPIC = "user-ownership-transfer-topic"; public static final String OBJECT = "object"; public static final String EDATA = "edata"; public static final String MANAGED_USERS = "managed_users"; 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 efef94ef13..f07066689a 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 @@ -148,7 +148,8 @@ public enum ActorOperations { DELETE_LOCATION_FROM_ES("deleteLocationDataFromES", "LBKGDEL"), ADD_ENCRYPTION_KEY("addEncryptionKey", "ADENCKEY"), USER_CURRENT_LOGIN("userCurrentLogin", "USRLOG"), - DELETE_USER("deleteUser", "USRDLT"); + DELETE_USER("deleteUser", "USRDLT"), + USER_OWNERSHIP_TRANSFER("userOwnershipTransfer","UOWNTRANS"); private String value; diff --git a/core/platform-common/src/main/resources/externalresource.properties b/core/platform-common/src/main/resources/externalresource.properties index 594cc0bc7b..a0d0959fb2 100644 --- a/core/platform-common/src/main/resources/externalresource.properties +++ b/core/platform-common/src/main/resources/externalresource.properties @@ -109,6 +109,6 @@ sunbird_password_reset_login_page_url=/resources isFormValidationRequired=true userProfileConfigMap={\"type\":\"profileconfig\",\"subtype\":\"28\",\"action\":\"get\",\"component\":\"*\",\"framework\":\"*\",\"data\":{\"templateName\":\"profileConfig_v2\",\"action\":\"get\",\"fields\":[{\"code\":\"persona\",\"children\":{\"administrator\":[{\"code\":\"district\"},{\"code\":\"state\"},{\"code\":\"subPersona\",\"type\":\"select\",\"default\":null,\"templateOptions\":{\"options\":[{\"label\":\"Headmaster\",\"value\":\"hm\"},{\"label\":\"Cluster Resource Person\",\"value\":\"crp\"}]}},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"teacher\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"student\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"parent\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"other\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"subPersona\",\"templateOptions\":{\"options\":[{\"value\":\"Doctor (Allopathy)\",\"label\":\"Doctor (Allopathy)\"},{\"value\":\"AYUSH Professional\",\"label\":\"AYUSH Professional\"}]}},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}]}}]},\"created_on\":\"2022-02-10T14:16:51.852Z\",\"last_modified_on\":\"2022-11-14T05:45:02.685Z\",\"rootOrgId\":\"*\"} sunbird_userorg_keyspace=sunbird - +user-ownership-transfer-topic={{env_name}}.user.ownership.transfer user-deletion-roles=public user-deletion-broadcast-topic={{env_name}}.delete.user \ No newline at end of file diff --git a/service/src/main/java/org/sunbird/actor/user/UserOwnershipTransferActor.java b/service/src/main/java/org/sunbird/actor/user/UserOwnershipTransferActor.java new file mode 100644 index 0000000000..93facffd2d --- /dev/null +++ b/service/src/main/java/org/sunbird/actor/user/UserOwnershipTransferActor.java @@ -0,0 +1,226 @@ +package org.sunbird.actor.user; + +import org.apache.commons.lang.StringUtils; +import org.sunbird.actor.core.BaseActor; +import org.sunbird.exception.ProjectCommonException; +import org.sunbird.exception.ResponseCode; +import org.sunbird.kafka.InstructionEventGenerator; +import org.sunbird.keys.JsonKey; +import org.sunbird.request.Request; +import org.sunbird.request.RequestContext; +import org.sunbird.response.Response; +import org.sunbird.response.ResponseParams; +import org.sunbird.service.user.UserRoleService; +import org.sunbird.service.user.UserService; +import org.sunbird.service.user.impl.UserRoleServiceImpl; +import org.sunbird.service.user.impl.UserServiceImpl; +import org.sunbird.util.ProjectUtil; +import org.sunbird.util.PropertiesCache; + +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static org.sunbird.validator.orgvalidator.BaseOrgRequestValidator.ERROR_CODE; + +public class UserOwnershipTransferActor extends BaseActor { + + private final UserRoleService userRoleService = UserRoleServiceImpl.getInstance(); + private final UserService userService = UserServiceImpl.getInstance(); + + @Override + public void onReceive(Request request) throws Throwable { + handleOwnershipTransfer(request); + } + + private void handleOwnershipTransfer(Request request) { + validateUserDetails(request.getRequest(), request.getRequestContext()); + String userId = (String) ((Map) request.getRequest().get(JsonKey.ACTION_BY)) + .get(JsonKey.USER_ID); + validateActionByUserRole(userId, request); + List> objects = getObjectsFromRequest(request); + if (!objects.isEmpty()) { + objects.forEach(object -> sendInstructionEvent(request, object)); + } else { + sendInstructionEvent(request, Collections.emptyMap()); + } + Response response = sendResponse("Ownership transfer process is submitted successfully!"); + sender().tell(response, self()); + } + + private void validateUserDetails(Map data, RequestContext requestContext) { + validateAndProceed(data, JsonKey.ACTION_BY, requestContext); + validateAndProceed(data, JsonKey.FROM_USER, requestContext); + validateAndProceed(data, JsonKey.TO_USER, requestContext); + } + + private void validateAndProceed(Map data, String key, RequestContext requestContext) { + if (data.containsKey(key)) { + validateUser(data.get(key), key, requestContext, data); + } else { + throwInvalidRequestDataException(key + " key is not present in the data."); + } + } + + private void validateUser(Object userNode, String userLabel, RequestContext requestContext, + Map data) { + if (userNode instanceof Map) { + Map user = (Map) userNode; + String userId = StringUtils.trimToNull(Objects.toString(user.get(JsonKey.USER_ID), "")); + String userName = StringUtils.trimToNull(Objects.toString(user.get(JsonKey.USERNAME), "")); + + if (StringUtils.isBlank(StringUtils.trimToNull(userId)) || + StringUtils.isBlank(StringUtils.trimToNull(userName))) { + throwInvalidRequestDataException("User id / user name key is not present in the " + userLabel); + } + + if (validUser(userId, requestContext)) { + validateAndFilterRoles(user, userLabel, data); + } else { + throwClientErrorException(); + } + } + } + + private void validateAndFilterRoles(Map user, String userLabel, Map data) { + if (!userLabel.equals(JsonKey.ACTION_BY) && !user.containsKey(JsonKey.ROLES)) { + throwInvalidRequestDataException("Roles key is not present for " + userLabel); + } + if (user.containsKey(JsonKey.ROLES)) { + Object roles = user.get(JsonKey.ROLES); + if (roles instanceof List) { + List rolesList = (List) roles; + if (rolesList.isEmpty()) { + throwInvalidRequestDataException("Roles are empty in " + userLabel + " details."); + } else { + List filteredRoles = filterRolesByOrganisationId((List) roles, + (String) data.get(JsonKey.ORGANISATION_ID)); + user.put(JsonKey.ROLES, filteredRoles); + } + } else { + throwDataTypeErrorException(); + } + } + } + + private List filterRolesByOrganisationId(List roles, String targetOrganisationId) { + List filteredRoles = new ArrayList<>(); + for (Object role : roles) { + if (role instanceof Map) { + Map roleMap = (Map) role; + List> scopeList = (List>) roleMap.get("scope"); + if (scopeList != null && !scopeList.isEmpty()) { + for (Map scope : scopeList) { + String organisationId = (String) scope.get("organisationId"); + if (targetOrganisationId.equals(organisationId)) { + filteredRoles.add((String) roleMap.get("role")); + break; + } + } + } + } + } + return filteredRoles; + } + + private void throwInvalidRequestDataException(String message) { + throw new ProjectCommonException( + ResponseCode.invalidRequestData, + message, + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + + private void throwClientErrorException() { + ProjectCommonException.throwClientErrorException( + ResponseCode.invalidParameter, + MessageFormat.format(ResponseCode.invalidParameter.getErrorMessage(), JsonKey.USER_ID)); + } + + private void throwDataTypeErrorException() { + throw new ProjectCommonException( + ResponseCode.dataTypeError, + ProjectUtil.formatMessage( + ResponseCode.dataTypeError.getErrorMessage(), JsonKey.ROLES, JsonKey.LIST), + ERROR_CODE); + } + + private boolean validUser(String userId, RequestContext context) { + return StringUtils.isNotBlank(userId) && userExists(userId, context); + } + + private boolean userExists(String userId, RequestContext context) { + try { + userService.getUserById(userId, context); + return true; + } catch (Exception ex) { + return false; + } + } + + private void validateActionByUserRole(String userId, Request request) { + List> userRoles = userRoleService.getUserRoles(userId, request.getRequestContext()); + boolean hasOrgAdminRole = userRoles.stream().anyMatch(role -> JsonKey.ORG_ADMIN.equals(role.get(JsonKey.ROLE))); + if (!hasOrgAdminRole) { + throw new ProjectCommonException( + ResponseCode.cannotTransferOwnership, + ResponseCode.cannotTransferOwnership.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + } + + private List> getObjectsFromRequest(Request request) { + return Optional.ofNullable((List>) request.getRequest().get("objects")) + .orElse(Collections.emptyList()); + } + + private void sendInstructionEvent(Request request, Map object) { + Map data = prepareEventData(request, object); + CompletableFuture.runAsync(() -> { + try { + PropertiesCache propertiesCache = PropertiesCache.getInstance(); + InstructionEventGenerator.pushInstructionEvent(propertiesCache.getProperty(JsonKey.USER_OWNERSHIP_TRANSFER_TOPIC), data); + } catch (Exception e) { + logger.error("Error pushing to instruction event", e); + } + }); + } + + private Map prepareEventData(Request request, Map object) { + Map actor = Map.of("id", "ownership-transfer", "type", "System"); + Map edataBase = Map.of( + JsonKey.ACTION, JsonKey.USER_OWNERSHIP_TRANSFER_ACTION, + JsonKey.ORGANISATION_ID, request.getRequest().get(JsonKey.ORGANISATION_ID), + JsonKey.CONTEXT, request.getRequest().get(JsonKey.CONTEXT), + JsonKey.ACTION_BY, request.getRequest().get(JsonKey.ACTION_BY), + JsonKey.FROM_USER_PROFILE, request.getRequest().get(JsonKey.FROM_USER), + JsonKey.TO_USER_PROFILE, request.getRequest().get(JsonKey.TO_USER) + ); + Map edata = new HashMap<>(edataBase); + Map assetInformation = new HashMap<>(object); + edata.put(JsonKey.ASSET_INFORMATION, assetInformation); + + Map result = new HashMap<>(); + Map fromUserDetails = (Map) request.getRequest().get(JsonKey.FROM_USER); + Map objectDetails = Map.of(JsonKey.ID, fromUserDetails.get(JsonKey.USER_ID), JsonKey.TYPE, + JsonKey.USER); + result.put("actor", actor); + result.put(JsonKey.OBJECT, objectDetails); + result.put(JsonKey.EDATA, edata); + return result; + } + + Response sendResponse(String statusMessage) { + Response response = new Response(); + response.setId("api.user.ownership.transfer"); + response.setVer("1.0"); + response.setTs(String.valueOf(Calendar.getInstance().getTime().getTime())); + ResponseParams params = new ResponseParams(); + params.setResmsgid(UUID.randomUUID().toString()); + params.setStatus(String.valueOf(ResponseParams.StatusType.SUCCESSFUL)); + response.setParams(params); + response.setResponseCode(ResponseCode.OK); + Map result = Map.of(JsonKey.STATUS, statusMessage); + response.putAll(result); + return response; + } +} diff --git a/service/src/test/java/org/sunbird/actor/user/UserOwnershipTransferActorTest.java b/service/src/test/java/org/sunbird/actor/user/UserOwnershipTransferActorTest.java new file mode 100644 index 0000000000..7a370bc311 --- /dev/null +++ b/service/src/test/java/org/sunbird/actor/user/UserOwnershipTransferActorTest.java @@ -0,0 +1,328 @@ +package org.sunbird.actor.user; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.dispatch.Futures; +import akka.testkit.TestActorRef; +import akka.testkit.javadsl.TestKit; +import org.junit.Before; +import org.junit.BeforeClass; +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.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.request.RequestContext; +import org.sunbird.response.Response; +import org.sunbird.service.user.UserRoleService; +import org.sunbird.service.user.UserService; +import org.sunbird.service.user.impl.UserRoleServiceImpl; +import org.sunbird.service.user.impl.UserServiceImpl; +import scala.concurrent.Promise; + +import java.text.MessageFormat; +import java.time.Duration; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ + ServiceFactory.class, + UserServiceImpl.class, + UserRoleServiceImpl.class +}) +@PowerMockIgnore({ + "javax.management.*", + "javax.net.ssl.*", + "javax.security.*", + "jdk.internal.reflect.*", + "javax.crypto.*" +}) +public class UserOwnershipTransferActorTest { + + private static final CassandraOperationImpl cassandraOperation = PowerMockito.mock(CassandraOperationImpl.class); + private static ActorSystem system; + Props props = Props.create(UserOwnershipTransferActor.class); + + private static Response getSuccessResponse() { + Response response = new Response(); + response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); + return response; + } + + @BeforeClass + public static void setup() { + system = ActorSystem.create("system"); + } + + @Before + public void beforeEachTest() { + mockStaticDependencies(); + } + + @Test + public void testOwnershipTransferSuccess() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + request.setRequestContext(new RequestContext()); + String statusMessage = "Ownership transfer process is submitted successfully!"; + subject.tell(request, probe.getRef()); + Response response = probe.expectMsgClass(Duration.ofSeconds(120), Response.class); + assertEquals(statusMessage, response.getResult().get(JsonKey.STATUS)); + } + + @Test + public void testOwnershipTransferWithEmptyObjectsList() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + request.getRequest().put("objects", new ArrayList<>()); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + Response response = probe.expectMsgClass(Duration.ofSeconds(120), Response.class); + assertEquals("Ownership transfer process is submitted successfully!", response.getResult().get(JsonKey.STATUS)); + } + + @Test + public void testInvalidUser() { + // Arrange + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequestWithInvalidUser(); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0028", errorResponse.getErrorCode()); + assertEquals("User id / user name key is not present in the fromUser", errorResponse.getMessage()); + assertEquals(400, errorResponse.getErrorResponseCode()); + } + + + private Request createTestRequestWithInvalidUser() { + Request request = createTestRequest(); + Map invalidUserDetails = new HashMap<>(); + invalidUserDetails.put("userName", "testUserName"); + request.getRequest().put("fromUser", invalidUserDetails); + return request; + } + + + @Test + public void testSendResponse() { + new TestKit(system) { + { + Props props = Props.create(UserOwnershipTransferActor.class); + TestActorRef ref = TestActorRef.create(system, props); + UserOwnershipTransferActor actor = ref.underlyingActor(); + String statusMessage = "Ownership transfer process is submitted successfully!"; + Response response = actor.sendResponse(statusMessage); + assertEquals("api.user.ownership.transfer", response.getId()); + assertEquals("1.0", response.getVer()); + assertEquals(statusMessage, response.getResult().get(JsonKey.STATUS)); + } + }; + } + + @Test + public void testInvalidUserDetails() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + request.getRequest().remove("fromUser"); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0028", errorResponse.getErrorCode()); + assertEquals("fromUser key is not present in the data.", errorResponse.getMessage()); + assertEquals(ResponseCode.CLIENT_ERROR.getResponseCode(), errorResponse.getErrorResponseCode()); + } + + + @Test + public void testInvalidRoleDetails() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + ((Map) request.getRequest().get("fromUser")).remove("roles"); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), + ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0028", errorResponse.getErrorCode()); + assertEquals("Roles key is not present for fromUser", errorResponse.getMessage()); + assertEquals(ResponseCode.CLIENT_ERROR.getResponseCode(), errorResponse.getErrorResponseCode()); + } + + @Test + public void testOwnershipTransferWithInvalidActorRoles() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + ((Map) request.getRequest().get(JsonKey.ACTION_BY)).put(JsonKey.USER_ID, "1234"); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0084", errorResponse.getErrorCode()); + assertEquals("User is restricted from transfering the ownership based on roles!", errorResponse.getMessage()); + assertEquals(ResponseCode.CLIENT_ERROR.getResponseCode(), errorResponse.getErrorResponseCode()); + } + + @Test + public void testOwnershipTransferWithEmptyRoles() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + ((Map) request.getRequest().get(JsonKey.FROM_USER)).put(JsonKey.ROLES, Collections.emptyList()); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0028", errorResponse.getErrorCode()); + assertEquals("Roles are empty in fromUser details.", errorResponse.getMessage()); + assertEquals(ResponseCode.CLIENT_ERROR.getResponseCode(), errorResponse.getErrorResponseCode()); + } + + @Test + public void testOwnershipTransferWithInvalidUserId() { + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + ((Map) request.getRequest().get(JsonKey.FROM_USER)).put(JsonKey.USER_ID, "123"); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), + ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0019", errorResponse.getErrorCode()); + assertEquals("Please provide valid userId.", errorResponse.getMessage()); + assertEquals(ResponseCode.CLIENT_ERROR.getResponseCode(), errorResponse.getErrorResponseCode()); + } + + + @Test + public void testInvalidActorDetails() { + // Test scenario where "actionBy" details are invalid (e.g., missing "userId" or "userName"). + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request request = createTestRequest(); + ((Map) request.getRequest().get("actionBy")).remove("userId"); + request.setRequestContext(new RequestContext()); + subject.tell(request, probe.getRef()); + ProjectCommonException errorResponse = probe.expectMsgClass(Duration.ofSeconds(120), + ProjectCommonException.class); + assertEquals("UOS_UOWNTRANS0028", errorResponse.getErrorCode()); + assertEquals("User id / user name key is not present in the actionBy", + errorResponse.getMessage()); + assertEquals(ResponseCode.CLIENT_ERROR.getResponseCode(), errorResponse.getErrorResponseCode()); + } + + private void mockStaticDependencies() { + mockStatic(ServiceFactory.class); + mockStatic(UserServiceImpl.class); + mockStatic(UserRoleServiceImpl.class); + UserService userServiceMock = mock(UserService.class); + UserRoleService userRoleServiceMock = mock(UserRoleService.class); + when(UserServiceImpl.getInstance()).thenReturn(userServiceMock); + when(UserRoleServiceImpl.getInstance()).thenReturn(userRoleServiceMock); + when(ServiceFactory.getInstance()).thenReturn(cassandraOperation); + Promise booleanPromise = Futures.promise(); + booleanPromise.success(true); + when(cassandraOperation.updateRecord(Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.any())) + .thenReturn(getSuccessResponse()); + when(cassandraOperation.insertRecord(Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.any())) + .thenReturn(getSuccessResponse()); + PowerMockito.when(userServiceMock.getUserById( + Mockito.argThat("123"::equals), Mockito.any(RequestContext.class))) + .thenThrow(new ProjectCommonException( + ResponseCode.resourceNotFound, + MessageFormat.format(ResponseCode.resourceNotFound.getErrorMessage(), JsonKey.USER), + ResponseCode.RESOURCE_NOT_FOUND.getResponseCode())); + Response response2 = new Response(); + Map user = new HashMap<>(); + user.put(JsonKey.ID, "c9e6006e-5811-4337-aa7c-48d0f535e3b8"); + user.put(JsonKey.IS_DELETED, false); + user.put(JsonKey.FIRST_NAME, "Testuser"); + user.put(JsonKey.STATUS, 1); + List> userList = new ArrayList<>(); + userList.add(user); + response2.getResult().put(JsonKey.RESPONSE, userList); + PowerMockito.when(cassandraOperation.getRecordById( + Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any())) + .thenReturn(response2); + Response mockResponse = new Response(); + List> mockRoles = new ArrayList<>(); + Map mockRoleData = new HashMap<>(); + mockRoleData.put(JsonKey.ROLE, "ORG_ADMIN"); + mockRoles.add(mockRoleData); + PowerMockito.when(userRoleServiceMock.getUserRoles( + Mockito.anyString(), Mockito.any(RequestContext.class))) + .thenReturn(mockRoles); + PowerMockito.when(userRoleServiceMock.getUserRoles( + eq("1234"), Mockito.any(RequestContext.class))) + .thenReturn(getInvalidRoleResponse()); + } + + private List> getInvalidRoleResponse() { + Map invalidUserRole = new HashMap<>(); + invalidUserRole.put(JsonKey.USER_ID, "1234"); + invalidUserRole.put(JsonKey.ROLE, "INVALID_ROLE"); + List> userRolesList = new ArrayList<>(); + userRolesList.add(invalidUserRole); + return userRolesList; + } + + + private Request createTestRequest() { + Request request = new Request(); + Map reqMap = new HashMap<>(); + reqMap.put("context", "User Deletion"); + reqMap.put("organisationId", "0137038836873134080"); + reqMap.put("actionBy", getUserDetailsMap()); + reqMap.put("fromUser", getUserDetailsMap()); + reqMap.put("toUser", getUserDetailsMap()); + reqMap.put("objects", getObjectsList()); + request.setRequest(reqMap); + request.setOperation(ActorOperations.USER_OWNERSHIP_TRANSFER.getValue()); + return request; + } + + private Map getUserDetailsMap() { + Map userDetails = new HashMap<>(); + userDetails.put("userId", "c9e6006e-5811-4337-aa7c-48d0f535e3b8"); + userDetails.put("userName", "Testuser"); + userDetails.put("roles", List.of("ORG_ADMIN")); + return userDetails; + } + + private List> getObjectsList() { + List> objects = new ArrayList<>(); + Map object1 = new HashMap<>(); + object1.put("objectType", "Content"); + object1.put("identifier", "do_id12"); + object1.put("primaryCategory", "ExplanationContent"); + object1.put("name", "TestContent"); + objects.add(object1); + + Map object2 = new HashMap<>(); + object2.put("objectType", "Program"); + object2.put("identifier", "programId1"); + object2.put("name", "TestProgram"); + objects.add(object2); + + return objects; + } +} diff --git a/service/src/test/resources/externalresource.properties b/service/src/test/resources/externalresource.properties index f939819ed3..ac392043e0 100644 --- a/service/src/test/resources/externalresource.properties +++ b/service/src/test/resources/externalresource.properties @@ -110,4 +110,5 @@ isFormValidationRequired=true userProfileConfigMap={\"type\":\"profileconfig\",\"subtype\":\"28\",\"action\":\"get\",\"component\":\"*\",\"framework\":\"*\",\"data\":{\"templateName\":\"profileConfig_v2\",\"action\":\"get\",\"fields\":[{\"code\":\"persona\",\"children\":{\"administrator\":[{\"code\":\"district\"},{\"code\":\"state\"},{\"code\":\"subPersona\",\"type\":\"select\",\"default\":null,\"templateOptions\":{\"options\":[{\"label\":\"Headmaster\",\"value\":\"hm\"},{\"label\":\"Cluster Resource Person\",\"value\":\"crp\"}]}},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"teacher\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"student\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"parent\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}],\"other\":[{\"code\":\"state\"},{\"code\":\"district\"},{\"code\":\"subPersona\",\"templateOptions\":{\"options\":[{\"value\":\"Doctor (Allopathy)\",\"label\":\"Doctor (Allopathy)\"},{\"value\":\"AYUSH Professional\",\"label\":\"AYUSH Professional\"}]}},{\"code\":\"block\"},{\"code\":\"cluster\"},{\"code\":\"school\"}]}}]},\"created_on\":\"2022-02-10T14:16:51.852Z\",\"last_modified_on\":\"2022-11-14T05:45:02.685Z\",\"rootOrgId\":\"*\"} sunbird_userorg_keyspace=sunbird sunbird_cloud_service_provider=azure -user-deletion-broadcast-topic=local.delete.user \ No newline at end of file +user-deletion-broadcast-topic=local.delete.user +user-ownership-transfer-topic=local.user.ownership.transfer