diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java index 3d80001be8..59d9e1f33a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java @@ -16,4 +16,5 @@ public interface CustomOrganizationRepository extends AppsmithRepository nextSlugNumber(String slugPrefix); + Mono updateUserRoleNames(String userId, String userName); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java index 9b45fa8d70..b96c0ff0b8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java @@ -1,6 +1,7 @@ package com.appsmith.server.repositories; import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.domains.Comment; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.QOrganization; import lombok.extern.slf4j.Slf4j; @@ -9,6 +10,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -66,4 +68,14 @@ public class CustomOrganizationRepositoryImpl extends BaseAppsmithRepositoryImpl }); } + @Override + public Mono updateUserRoleNames(String userId, String userName) { + return mongoOperations + .updateMulti( + Query.query(Criteria.where("userRoles.userId").is(userId)), + Update.update("userRoles.$.name", userName), + Organization.class + ) + .then(); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java index 6d2224f775..76e51eaf35 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java @@ -13,4 +13,6 @@ public interface OrganizationRepository extends BaseRepository findByName(String name); + Mono updateUserRoleNames(String userId, String userName); + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java index e45b431d1b..d8659fe97b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java @@ -1,5 +1,6 @@ package com.appsmith.server.services; +import com.appsmith.external.models.BaseDomain; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.acl.RoleGraph; @@ -35,6 +36,7 @@ import javax.validation.Validator; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +import java.util.ArrayList; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java index 921580c22e..8fa28c55f9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java @@ -3,6 +3,7 @@ package com.appsmith.server.solutions; import com.appsmith.server.domains.User; import com.appsmith.server.events.UserChangedEvent; import com.appsmith.server.repositories.CommentRepository; +import com.appsmith.server.repositories.OrganizationRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; @@ -20,6 +21,7 @@ public class UserChangedHandler { private final ApplicationEventPublisher applicationEventPublisher; private final CommentRepository commentRepository; + private final OrganizationRepository organizationRepository; public User publish(User user) { applicationEventPublisher.publishEvent(new UserChangedEvent(user)); @@ -35,6 +37,10 @@ public class UserChangedHandler { updateNameInComments(user) .subscribeOn(Schedulers.elastic()) .subscribe(); + + updateNameInUserRoles(user) + .subscribeOn(Schedulers.elastic()) + .subscribe(); } private Mono updateNameInComments(User user) { @@ -47,4 +53,13 @@ public class UserChangedHandler { return commentRepository.updateAuthorNames(user.getId(), user.getName()); } + private Mono updateNameInUserRoles(User user) { + if (user.getId() == null) { + log.warn("Attempt to update name in userRoles of organization for user with null ID."); + return Mono.empty(); + } + + log.debug("Updating name in userRoles of organization for user {}", user.getId()); + return organizationRepository.updateUserRoleNames(user.getId(), user.getName()); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java new file mode 100644 index 0000000000..b36cb6edbd --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java @@ -0,0 +1,83 @@ +package com.appsmith.server.repositories; + +import com.appsmith.server.domains.Organization; +import com.appsmith.server.domains.UserRole; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import reactor.util.function.Tuple2; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +@DirtiesContext +class OrganizationRepositoryTest { + + @Autowired + private OrganizationRepository organizationRepository; + + @Test + void updateUserRoleNames_WhenUserIdMatched_AllOrgsUpdated() { + String oldUserName = "Old name", + newUserName = "New name", + userId = "user1"; + UserRole userRole = new UserRole(); + userRole.setName(oldUserName); + userRole.setUserId(userId); + + List userRoles = new ArrayList<>(); + userRoles.add(userRole); + + Organization org1 = new Organization(); + org1.setId(UUID.randomUUID().toString()); + org1.setSlug(org1.getId()); + org1.setUserRoles(userRoles); + + Organization org2 = new Organization(); + org2.setId(UUID.randomUUID().toString()); + org2.setSlug(org2.getId()); + org2.setUserRoles(userRoles); + + // create two orgs + Mono> aveOrgsMonoZip = Mono.zip( + organizationRepository.save(org1), organizationRepository.save(org2) + ); + + Mono> updatedOrgTupleMono = aveOrgsMonoZip.flatMap(objects -> { + // update the user names + return organizationRepository.updateUserRoleNames(userId, newUserName).thenReturn(objects); + }).flatMap(organizationTuple2 -> { + // fetch the two orgs again + Mono updatedOrg1Mono = organizationRepository.findBySlug(org1.getId()); + Mono updatedOrg2Mono = organizationRepository.findBySlug(org2.getId()); + return Mono.zip(updatedOrg1Mono, updatedOrg2Mono); + }); + + StepVerifier.create(updatedOrgTupleMono).assertNext(orgTuple -> { + Organization o1 = orgTuple.getT1(); + Assert.assertEquals(1, o1.getUserRoles().size()); + UserRole userRole1 = o1.getUserRoles().get(0); + Assert.assertEquals(userId, userRole1.getUserId()); + Assert.assertEquals(newUserName, userRole1.getName()); + + Organization o2 = orgTuple.getT2(); + Assert.assertEquals(1, o2.getUserRoles().size()); + UserRole userRole2 = o2.getUserRoles().get(0); + Assert.assertEquals(userId, userRole2.getUserId()); + Assert.assertEquals(newUserName, userRole2.getName()); + }).verifyComplete(); + } +} \ No newline at end of file diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java new file mode 100644 index 0000000000..452745efbf --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java @@ -0,0 +1,97 @@ +package com.appsmith.server.services; + +import com.appsmith.server.acl.RoleGraph; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.Organization; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.UserRole; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.repositories.AssetRepository; +import com.appsmith.server.repositories.OrganizationRepository; +import com.appsmith.server.repositories.PluginRepository; +import com.appsmith.server.repositories.UserRepository; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.test.StepVerifier; + +import javax.validation.Validator; +import java.util.ArrayList; +import java.util.List; + +import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS; + +@RunWith(SpringJUnit4ClassRunner.class) +public class OrganizationServiceUnitTest { + + @MockBean PluginRepository pluginRepository; + @MockBean SessionUserService sessionUserService; + @MockBean UserOrganizationService userOrganizationService; + @MockBean UserRepository userRepository; + @MockBean RoleGraph roleGraph; + @MockBean AssetRepository assetRepository; + @MockBean AssetService assetService; + @MockBean Scheduler scheduler; + @MockBean MongoConverter mongoConverter; + @MockBean ReactiveMongoTemplate reactiveMongoTemplate; + @MockBean OrganizationRepository organizationRepository; + @MockBean Validator validator; + @MockBean AnalyticsService analyticsService; + + OrganizationService organizationService; + + @Before + public void setUp() { + organizationService = new OrganizationServiceImpl(scheduler, validator, mongoConverter, reactiveMongoTemplate, + organizationRepository, analyticsService, pluginRepository, sessionUserService, userOrganizationService, + userRepository, roleGraph, assetRepository, assetService + ); + MockitoAnnotations.initMocks(this); + } + + @Test + public void getOrganizationMembers_WhenRoleIsNull_ReturnsEmptyList() { + // create a organization object + Organization testOrg = new Organization(); + testOrg.setName("Get All Members For Organization Test"); + testOrg.setDomain("test.com"); + testOrg.setWebsite("https://test.com"); + testOrg.setId("test-org-id"); + + // mock repository methods so that they return the objects we've created + Mockito.when(organizationRepository.findById("test-org-id", ORGANIZATION_INVITE_USERS)) + .thenReturn(Mono.just(testOrg)); + + Mono> organizationMembers = organizationService.getOrganizationMembers(testOrg.getId()); + StepVerifier + .create(organizationMembers) + .assertNext(userRoles -> { + Assert.assertEquals(0, userRoles.size()); + }) + .verifyComplete(); + } + + @Test + public void getOrganizationMembers_WhenNoOrgFound_ThrowsException() { + String sampleOrgId = "test-org-id"; + // mock repository methods so that they return the objects we've created + Mockito.when(organizationRepository.findById(sampleOrgId, ORGANIZATION_INVITE_USERS)) + .thenReturn(Mono.empty()); + + Mono> organizationMembers = organizationService.getOrganizationMembers(sampleOrgId); + StepVerifier + .create(organizationMembers) + .expectErrorMessage(AppsmithError.NO_RESOURCE_FOUND.getMessage(FieldName.ORGANIZATION, sampleOrgId)) + .verify(); + } +}