typeReference) {
return super.post(uri, body, typeReference);
diff --git a/src/main/java/com/descope/sdk/mgmt/UserService.java b/src/main/java/com/descope/sdk/mgmt/UserService.java
index cf66c826..4e410f06 100644
--- a/src/main/java/com/descope/sdk/mgmt/UserService.java
+++ b/src/main/java/com/descope/sdk/mgmt/UserService.java
@@ -4,6 +4,7 @@
import com.descope.exception.DescopeException;
import com.descope.model.auth.InviteOptions;
import com.descope.model.user.request.BatchUserRequest;
+import com.descope.model.user.request.PatchUserRequest;
import com.descope.model.user.request.UserRequest;
import com.descope.model.user.request.UserSearchRequest;
import com.descope.model.user.response.AllUsersResponseDetails;
@@ -90,6 +91,7 @@ public interface UserService {
*
* IMPORTANT: All parameters will override whatever values are currently set in the existing
* user. Use carefully.
+ * Instead, use Patch if you don't want to pass all parameters.
*
* @param loginId The loginID is required and will determine what the user will use to sign in,
* make sure the login id is unique for test.
@@ -100,6 +102,18 @@ public interface UserService {
*/
UserResponseDetails update(String loginId, UserRequest request) throws DescopeException;
+ /**
+ * Patches an existing user.
+ *
+ *
Only the fields that are set in the request will be updated.
+ *
+ * @param loginId The loginID is required and will determine which user to update.
+ * @param request The request containing the fields to be updated. Fields not set will remain unchanged.
+ * @return {@link UserResponseDetails UserResponseDetails} containing the updated user details.
+ * @throws DescopeException If there occurs any exception, a subtype of this exception will be thrown.
+ */
+ UserResponseDetails patch(String loginId, PatchUserRequest request) throws DescopeException;
+
/**
* Logout user from all devices.
*
diff --git a/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java b/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java
index 77f1a562..14dfd0dc 100644
--- a/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java
+++ b/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java
@@ -10,6 +10,7 @@
import static com.descope.literals.Routes.ManagementEndPoints.LOAD_USER_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.LOGOUT_USER_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.MAGIC_LINK_FOR_TEST_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.PATCH_USER_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.UPDATE_CUSTOM_ATTRIBUTE_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.UPDATE_PICTURE_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.UPDATE_USER_LINK;
@@ -46,6 +47,7 @@
import com.descope.model.user.request.GenerateEmbeddedLinkRequest;
import com.descope.model.user.request.MagicLinkTestUserRequest;
import com.descope.model.user.request.OTPTestUserRequest;
+import com.descope.model.user.request.PatchUserRequest;
import com.descope.model.user.request.UserRequest;
import com.descope.model.user.request.UserSearchRequest;
import com.descope.model.user.response.AllUsersResponseDetails;
@@ -151,6 +153,21 @@ public UsersBatchResponse inviteBatch(List users, InviteOption
return apiProxy.post(createUsersUri, req, UsersBatchResponse.class);
}
+ @Override
+ public UserResponseDetails patch(String loginId, PatchUserRequest request) throws DescopeException {
+ if (StringUtils.isBlank(loginId)) {
+ throw ServerCommonException.invalidArgument("Login ID");
+ }
+ if (request == null) {
+ request = new PatchUserRequest();
+ }
+ Map req = mapOf("loginId", loginId);
+ req.putAll(request.toMap());
+ URI patchUserUri = composePatchUserUri();
+ ApiProxy apiProxy = getApiProxy();
+ return apiProxy.patch(patchUserUri, req, UserResponseDetails.class);
+ }
+
@Override
public UserResponseDetails update(String loginId, UserRequest request) throws DescopeException {
if (StringUtils.isBlank(loginId)) {
@@ -583,7 +600,7 @@ public List history(List userIds) throws DescopeExc
}
ApiProxy apiProxy = getApiProxy();
return apiProxy.postAndGetArray(getUri(USER_HISTORY_LINK), userIds,
- new TypeReference>() {});
+ new TypeReference>() {});
}
public String generateEmbeddedLink(
@@ -594,8 +611,8 @@ public String generateEmbeddedLink(
URI generateEmbeddedLinkUri = composeGenerateEmbeddedLink();
GenerateEmbeddedLinkRequest request = new GenerateEmbeddedLinkRequest(loginId, customClaims);
ApiProxy apiProxy = getApiProxy();
- GenerateEmbeddedLinkResponse response =
- apiProxy.post(generateEmbeddedLinkUri, request, GenerateEmbeddedLinkResponse.class);
+ GenerateEmbeddedLinkResponse response = apiProxy.post(generateEmbeddedLinkUri, request,
+ GenerateEmbeddedLinkResponse.class);
return response.getToken();
}
@@ -607,6 +624,10 @@ private URI composeCreateBatchUsersUri() {
return getUri(CREATE_USERS_BATCH_LINK);
}
+ private URI composePatchUserUri() {
+ return getUri(PATCH_USER_LINK);
+ }
+
private URI composeUpdateUserUri() {
return getUri(UPDATE_USER_LINK);
}
diff --git a/src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java b/src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java
index 8eaf7828..8d7988e4 100644
--- a/src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java
+++ b/src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java
@@ -8,11 +8,13 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.descope.enums.BatchUserPasswordAlgorithm;
import com.descope.enums.DeliveryMethod;
@@ -27,6 +29,7 @@
import com.descope.model.mgmt.ManagementServices;
import com.descope.model.user.request.BatchUserPasswordHashed;
import com.descope.model.user.request.BatchUserRequest;
+import com.descope.model.user.request.PatchUserRequest;
import com.descope.model.user.request.UserRequest;
import com.descope.model.user.request.UserSearchRequest;
import com.descope.model.user.response.AllUsersResponseDetails;
@@ -162,6 +165,61 @@ void testUpdateForSuccess() {
}
}
+ @Test
+ void testPatchForEmptyLoginId() {
+ ServerCommonException thrown = assertThrows(ServerCommonException.class,
+ () -> userService.patch("", new PatchUserRequest()));
+ assertNotNull(thrown);
+ assertEquals("The Login ID argument is invalid", thrown.getMessage());
+ }
+
+ @Test
+ void testUserPatchError() {
+ UserService userService = mock(UserService.class);
+ PatchUserRequest user = new PatchUserRequest();
+ String email = "foo@bar.com";
+ user.setEmail(email);
+
+ when(userService.patch(anyString(), any(PatchUserRequest.class))).thenThrow(new RuntimeException("Error"));
+
+ assertThrows(RuntimeException.class, () -> userService.patch("123", user));
+ }
+
+ @Test
+ void testUserPatchSuccess() {
+ PatchUserRequest user = new PatchUserRequest();
+ user.setName("name1");
+ user.setMiddleName("middleName1");
+ user.setPhone("+9724567890");
+ user.setVerifiedPhone(true);
+ user.setPicture("https://test.com");
+ user.setRoleNames(Arrays.asList("foo", "bar"));
+
+ UserResponseDetails expectedResponse = new UserResponseDetails();
+ expectedResponse.setUser(new UserResponse());
+ expectedResponse.getUser().setName("name1");
+ expectedResponse.getUser().setMiddleName("middleName1");
+ expectedResponse.getUser().setPhone("+9724567890");
+ expectedResponse.getUser().setVerifiedPhone(true);
+ expectedResponse.getUser().setPicture("https://test.com");
+ expectedResponse.getUser().setRoleNames(Arrays.asList("foo", "bar"));
+
+ ApiProxy apiProxy = mock(ApiProxy.class);
+ doReturn(expectedResponse).when(apiProxy).patch(any(), any(), any());
+ try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {
+ mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
+ UserResponseDetails response = userService.patch("someLoginId", user);
+ Assertions.assertThat(response).isNotNull();
+ assertNotNull(response.getUser());
+ assertEquals(expectedResponse.getUser().getName(), response.getUser().getName());
+ assertEquals(expectedResponse.getUser().getMiddleName(), response.getUser().getMiddleName());
+ assertEquals(expectedResponse.getUser().getPhone(), response.getUser().getPhone());
+ assertTrue(response.getUser().getVerifiedPhone());
+ assertEquals(expectedResponse.getUser().getPicture(), response.getUser().getPicture());
+ assertEquals(expectedResponse.getUser().getRoleNames(), response.getUser().getRoleNames());
+ }
+ }
+
@Test
void testLogoutForEmptyLoginId() {
ServerCommonException thrown = assertThrows(ServerCommonException.class, () -> userService.logoutUser(""));
@@ -704,8 +762,8 @@ void testGenerateMagicLinkForTestUserForSuccess() {
doReturn(mockResponse).when(apiProxy).post(any(), any(), any());
try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {
mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
- MagicLinkTestUserResponse response =
- userService.generateMagicLinkForTestUser("someLoginId", mockUrl, DeliveryMethod.EMAIL);
+ MagicLinkTestUserResponse response = userService.generateMagicLinkForTestUser("someLoginId", mockUrl,
+ DeliveryMethod.EMAIL);
Assertions.assertThat(response.getLink()).isEqualTo("link");
}
}
@@ -787,9 +845,9 @@ void testFunctionalFullCycle() {
String phone = "+1-555-555-5555";
List additionalLoginIds = Arrays.asList(TestUtils.getRandomName("u-"), TestUtils.getRandomName("u-"));
// Create
- UserResponseDetails createResponse = userService.create(loginId, UserRequest.builder().email(email)
- .verifiedEmail(true).phone(phone).verifiedPhone(true).displayName("Testing Test")
- .additionalLoginIds(additionalLoginIds).build());
+ UserResponseDetails createResponse = userService.create(loginId,
+ UserRequest.builder().email(email).verifiedEmail(true).phone(phone).verifiedPhone(true)
+ .displayName("Testing Test").additionalLoginIds(additionalLoginIds).build());
UserResponse user = createResponse.getUser();
assertNotNull(user);
Assertions.assertThat(user.getLoginIds()).contains(loginId);
@@ -854,6 +912,12 @@ void testFunctionalFullCycle() {
}
assertTrue(found);
assertTrue(searchResponse.getTotal() > 0);
+ PatchUserRequest pur = new PatchUserRequest();
+ pur.setFamilyName("hurray patch works");
+ userService.patch(newLoginId, pur);
+
+ loadResponse = userService.loadByUserId(createResponse.getUser().getUserId());
+ assertEquals(loadResponse.getUser().getFamilyName(), pur.getFamilyName());
// Delete
userService.delete(newLoginId);
}
@@ -864,9 +928,8 @@ void testFunctionalTestUsers() {
String email = TestUtils.getRandomName("test-") + "@descope.com";
String phone = "+1-555-555-5555";
// Create
- UserResponseDetails createResponse = userService.createTestUser(loginId, UserRequest.builder()
- .email(email).verifiedEmail(true).phone(phone).verifiedPhone(true)
- .displayName("Testing Test").build());
+ UserResponseDetails createResponse = userService.createTestUser(loginId, UserRequest.builder().email(email)
+ .verifiedEmail(true).phone(phone).verifiedPhone(true).displayName("Testing Test").build());
UserResponse user = createResponse.getUser();
assertNotNull(user);
Assertions.assertThat(user.getLoginIds()).contains(loginId);
@@ -918,7 +981,7 @@ void testFunctionalUserWithTenantAndRole() {
UserRequest.builder().email(email).verifiedEmail(true).phone(phone).verifiedPhone(true)
.displayName("Testing Test")
.userTenants(
- Arrays.asList(AssociatedTenant.builder().tenantId(tenantId).roleNames(Arrays.asList(roleName)).build()))
+ Arrays.asList(AssociatedTenant.builder().tenantId(tenantId).roleNames(Arrays.asList(roleName)).build()))
.build());
UserResponse user = createResponse.getUser();
assertNotNull(user);
@@ -929,12 +992,11 @@ void testFunctionalUserWithTenantAndRole() {
assertEquals(true, user.getVerifiedPhone());
assertEquals("Testing Test", user.getName());
assertEquals("invited", user.getStatus());
- assertThat(user.getUserTenants()).containsExactly(
- AssociatedTenant.builder().tenantId(tenantId).tenantName(tenantName).roleNames(
- Arrays.asList(roleName)).build());
+ assertThat(user.getUserTenants()).containsExactly(AssociatedTenant.builder().tenantId(tenantId)
+ .tenantName(tenantName).roleNames(Arrays.asList(roleName)).build());
UserResponseDetails updateResponse = userService.update(loginId,
- UserRequest.builder().roleNames(Arrays.asList(roleName)).email(email).verifiedEmail(true)
- .phone(phone).verifiedPhone(true).displayName("Testing Test").build());
+ UserRequest.builder().roleNames(Arrays.asList(roleName)).email(email).verifiedEmail(true).phone(phone)
+ .verifiedPhone(true).displayName("Testing Test").build());
user = updateResponse.getUser();
assertNotNull(user);
assertThat(user.getRoleNames()).containsExactly(roleName);
@@ -1003,8 +1065,8 @@ void testFunctionalGenerateEmbeddedLinkWithPhoneAsID() {
String phone = "+1-555-555-" + randomSaffix;
String cleanPhone = "+1555555" + randomSaffix;
// Create
- UserResponseDetails createResponse = userService.create(phone, UserRequest.builder().phone(phone)
- .verifiedPhone(true).displayName("Testing Test").build());
+ UserResponseDetails createResponse = userService.create(phone,
+ UserRequest.builder().phone(phone).verifiedPhone(true).displayName("Testing Test").build());
UserResponse user = createResponse.getUser();
assertNotNull(user);
Assertions.assertThat(user.getLoginIds()).contains(cleanPhone);
@@ -1033,19 +1095,11 @@ void testFunctionalBatch() throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = factory.generateSecret(spec).getEncoded();
- UsersBatchResponse res = userService.createBatch(Arrays.asList(BatchUserRequest.builder()
- .loginId(loginId)
- .email(email)
- .verifiedEmail(true)
- .phone(phone)
- .verifiedPhone(true)
- .displayName(name)
+ UsersBatchResponse res = userService.createBatch(Arrays.asList(BatchUserRequest.builder().loginId(loginId)
+ .email(email).verifiedEmail(true).phone(phone).verifiedPhone(true).displayName(name)
.hashedPassword(BatchUserPasswordHashed.builder()
- .algorithm(BatchUserPasswordAlgorithm.BATCH_USER_PASSWORD_ALGORITHM_PBKDF2SHA1)
- .hash(hash)
- .salt(salt)
- .iterations(65536)
- .build())
+ .algorithm(BatchUserPasswordAlgorithm.BATCH_USER_PASSWORD_ALGORITHM_PBKDF2SHA1).hash(hash).salt(salt)
+ .iterations(65536).build())
.build()));
assertNotNull(res);
assertNotNull(res.getCreatedUsers());
@@ -1065,13 +1119,8 @@ void testFunctionalSetActivePassword() {
String email = TestUtils.getRandomName("test-") + "@descope.com";
String phone = "+1-555-555-5555";
// Create
- UserResponseDetails createResponse = userService.create(loginId, UserRequest.builder()
- .email(email)
- .verifiedEmail(true)
- .phone(phone)
- .verifiedPhone(true)
- .displayName("Testing Test")
- .build());
+ UserResponseDetails createResponse = userService.create(loginId, UserRequest.builder().email(email)
+ .verifiedEmail(true).phone(phone).verifiedPhone(true).displayName("Testing Test").build());
UserResponse user = createResponse.getUser();
assertNotNull(user);
Assertions.assertThat(user.getLoginIds()).contains(loginId);