From 41023d5d6dfb5b6f5e0ba46989ea6c4df3b98340 Mon Sep 17 00:00:00 2001 From: denislavstanchev Date: Thu, 4 Dec 2025 13:16:14 +0200 Subject: [PATCH 1/5] Add support for user POST /users/{userId}/deactivate and POST /users/{userId}/reactivate endpoints --- .../com/smartsheet/api/UserResources.java | 40 ++++++++++++++++ .../api/internal/UserResourcesImpl.java | 46 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/main/java/com/smartsheet/api/UserResources.java b/src/main/java/com/smartsheet/api/UserResources.java index ea3c0427..4b198b74 100644 --- a/src/main/java/com/smartsheet/api/UserResources.java +++ b/src/main/java/com/smartsheet/api/UserResources.java @@ -294,6 +294,46 @@ PagedResult listUsers(Set email, Long planId, */ void deleteUser(long id, DeleteUserParameters parameters) throws SmartsheetException; + /** + *

Reactivates a user.

+ * + *

Reactivates the user associated with the current Smartsheet plan, restoring the user's access to + * Smartsheet, owned items, and shared items.

+ * + *

Important: You can reactivate the user only if that user has been deactivated for less than thirty (30) days.

+ * + *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/reactivate

+ * + * @param id the id of the user to reactivate + * @return the result object + * @throws IllegalArgumentException if any argument is null or empty string + * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain) + * @throws AuthorizationException if there is any problem with the REST API authorization (access token) + * @throws ResourceNotFoundException if the resource cannot be found + * @throws ServiceUnavailableException if the REST API service is not available (possibly due to rate limiting) + * @throws SmartsheetException if there is any other error during the operation + */ + void reactivateUser(long id) throws SmartsheetException; + + /** + *

Deactivates a user.

+ * + *

Deactivates the user associated with the current Smartsheet plan, blocking the user from using Smartsheet in any way. + * Deactivating a user does not affect their existing permissions on owned or shared items.

+ * + *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/deactivate

+ * + * @param id the id of the user to deactivate + * @return the result object + * @throws IllegalArgumentException if any argument is null or empty string + * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain or user is managed by external source) + * @throws AuthorizationException if there is any problem with the REST API authorization (access token) + * @throws ResourceNotFoundException if the resource cannot be found + * @throws ServiceUnavailableException if the REST API service is not available (possibly due to rate limiting) + * @throws SmartsheetException if there is any other error during the operation + */ + void deactivateUser(long id) throws SmartsheetException; + /** *

List all organisation sheets.

* diff --git a/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java b/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java index 05db0fa5..db30b579 100644 --- a/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java +++ b/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java @@ -684,6 +684,52 @@ private void changeSeatType(String seatType, String path) throws SmartsheetExcep createResource(path, Result.class, body); } + /** + *

Reactivates a user.

+ * + *

Reactivates the user associated with the current Smartsheet plan, restoring the user's access to + * Smartsheet, owned items, and shared items.

+ * + *

Important: You can reactivate the user only if that user has been deactivated for less than thirty (30) days.

+ * + *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/reactivate

+ * + * @param userId the id of the user to reactivate + * @return the result object + * @throws IllegalArgumentException if any argument is null or empty string + * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain) + * @throws AuthorizationException if there is any problem with the REST API authorization (access token) + * @throws ResourceNotFoundException if the resource cannot be found + * @throws ServiceUnavailableException if the REST API service is not available (possibly due to rate limiting) + * @throws SmartsheetException if there is any other error during the operation + */ + @Override + public void reactivateUser(long userId) throws SmartsheetException { + createResource(USERS + "/" + userId + "/reactivate", Result.class, Collections.emptyMap()); + } + + /** + *

Deactivates a user.

+ * + *

Deactivates the user associated with the current Smartsheet plan, blocking the user from using Smartsheet in any way. + * Deactivating a user does not affect their existing permissions on owned or shared items.

+ * + *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/deactivate

+ * + * @param userId the id of the user to deactivate + * @return the result object + * @throws IllegalArgumentException if any argument is null or empty string + * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain or user is managed by external source) + * @throws AuthorizationException if there is any problem with the REST API authorization (access token) + * @throws ResourceNotFoundException if the resource cannot be found + * @throws ServiceUnavailableException if the REST API service is not available (possibly due to rate limiting) + * @throws SmartsheetException if there is any other error during the operation + */ + @Override + public void deactivateUser(long userId) throws SmartsheetException { + createResource(USERS + "/" + userId + "/deactivate", Result.class, Collections.emptyMap()); + } + @Override public void deleteUser(long userId, DeleteUserParameters parameters) throws SmartsheetException { String path = USERS + "/" + userId; From 143ed4e243ac88053044b7f441b53c3b54f085a8 Mon Sep 17 00:00:00 2001 From: denislavstanchev Date: Thu, 4 Dec 2025 13:18:49 +0200 Subject: [PATCH 2/5] Fix docs --- src/main/java/com/smartsheet/api/UserResources.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/smartsheet/api/UserResources.java b/src/main/java/com/smartsheet/api/UserResources.java index 4b198b74..187c82c7 100644 --- a/src/main/java/com/smartsheet/api/UserResources.java +++ b/src/main/java/com/smartsheet/api/UserResources.java @@ -305,7 +305,6 @@ PagedResult listUsers(Set email, Long planId, *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/reactivate

* * @param id the id of the user to reactivate - * @return the result object * @throws IllegalArgumentException if any argument is null or empty string * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain) * @throws AuthorizationException if there is any problem with the REST API authorization (access token) @@ -324,7 +323,6 @@ PagedResult listUsers(Set email, Long planId, *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/deactivate

* * @param id the id of the user to deactivate - * @return the result object * @throws IllegalArgumentException if any argument is null or empty string * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain or user is managed by external source) * @throws AuthorizationException if there is any problem with the REST API authorization (access token) From 5ee0d339dcec82aa46d305a42595ea5adbb84bf0 Mon Sep 17 00:00:00 2001 From: denislavstanchev Date: Thu, 4 Dec 2025 13:19:32 +0200 Subject: [PATCH 3/5] Fix docs in implementation --- .../java/com/smartsheet/api/internal/UserResourcesImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java b/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java index db30b579..e0105e16 100644 --- a/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java +++ b/src/main/java/com/smartsheet/api/internal/UserResourcesImpl.java @@ -695,7 +695,6 @@ private void changeSeatType(String seatType, String path) throws SmartsheetExcep *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/reactivate

* * @param userId the id of the user to reactivate - * @return the result object * @throws IllegalArgumentException if any argument is null or empty string * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain) * @throws AuthorizationException if there is any problem with the REST API authorization (access token) @@ -717,7 +716,6 @@ public void reactivateUser(long userId) throws SmartsheetException { *

It mirrors to the following Smartsheet REST API method: POST /users/{userId}/deactivate

* * @param userId the id of the user to deactivate - * @return the result object * @throws IllegalArgumentException if any argument is null or empty string * @throws InvalidRequestException if there is any problem with the REST API request (e.g., user email belongs to ISP domain or user is managed by external source) * @throws AuthorizationException if there is any problem with the REST API authorization (access token) From 20eb1d97df74d5adb5fa34ea8566316b72fea688 Mon Sep 17 00:00:00 2001 From: denislavstanchev Date: Thu, 4 Dec 2025 13:39:31 +0200 Subject: [PATCH 4/5] Add wiremock tests --- .../api/sdktest/users/TestDeactivateUser.java | 90 +++++++++++++++++++ .../api/sdktest/users/TestReactivateUser.java | 90 +++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 src/test/java/com/smartsheet/api/sdktest/users/TestDeactivateUser.java create mode 100644 src/test/java/com/smartsheet/api/sdktest/users/TestReactivateUser.java diff --git a/src/test/java/com/smartsheet/api/sdktest/users/TestDeactivateUser.java b/src/test/java/com/smartsheet/api/sdktest/users/TestDeactivateUser.java new file mode 100644 index 00000000..e1f6b82a --- /dev/null +++ b/src/test/java/com/smartsheet/api/sdktest/users/TestDeactivateUser.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2025 Smartsheet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartsheet.api.sdktest.users; + +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.smartsheet.api.Smartsheet; +import com.smartsheet.api.SmartsheetException; +import com.smartsheet.api.WiremockClient; +import com.smartsheet.api.WiremockClientWrapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.UUID; + +import static com.smartsheet.api.sdktest.users.CommonTestConstants.TEST_USER_ID; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestDeactivateUser { + @Test + void testDeactivateUserGeneratedUrlIsCorrect() throws SmartsheetException { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/users/deactivate-user/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + WiremockClient wiremockClient = wrapper.getWiremockClient(); + + smartsheet.userResources().deactivateUser(TEST_USER_ID); + LoggedRequest wiremockRequest = wiremockClient.findWiremockRequest(requestId); + String path = URI.create(wiremockRequest.getUrl()).getPath(); + + assertThat(path).isEqualTo("/2.0/users/" + TEST_USER_ID + "/deactivate"); + } + + @Test + void testDeactivateUserAllResponseBodyProperties() throws SmartsheetException { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/users/deactivate-user/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + Assertions.assertDoesNotThrow(() -> { + smartsheet.userResources().deactivateUser(TEST_USER_ID); + }); + } + + @Test + void testDeactivateUserError500Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/500-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.userResources().deactivateUser(TEST_USER_ID); + }); + + assertThat(exception.getMessage()).isEqualTo("Internal Server Error"); + } + + @Test + void testDeactivateUserError400Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/400-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.userResources().deactivateUser(TEST_USER_ID); + }); + + assertThat(exception.getMessage()).isEqualTo("Malformed Request"); + } +} diff --git a/src/test/java/com/smartsheet/api/sdktest/users/TestReactivateUser.java b/src/test/java/com/smartsheet/api/sdktest/users/TestReactivateUser.java new file mode 100644 index 00000000..cdb4f45c --- /dev/null +++ b/src/test/java/com/smartsheet/api/sdktest/users/TestReactivateUser.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2025 Smartsheet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.smartsheet.api.sdktest.users; + +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.smartsheet.api.Smartsheet; +import com.smartsheet.api.SmartsheetException; +import com.smartsheet.api.WiremockClient; +import com.smartsheet.api.WiremockClientWrapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.UUID; + +import static com.smartsheet.api.sdktest.users.CommonTestConstants.TEST_USER_ID; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestReactivateUser { + @Test + void testReactivateUserGeneratedUrlIsCorrect() throws SmartsheetException { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/users/reactivate-user/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + WiremockClient wiremockClient = wrapper.getWiremockClient(); + + smartsheet.userResources().reactivateUser(TEST_USER_ID); + LoggedRequest wiremockRequest = wiremockClient.findWiremockRequest(requestId); + String path = URI.create(wiremockRequest.getUrl()).getPath(); + + assertThat(path).isEqualTo("/2.0/users/" + TEST_USER_ID + "/reactivate"); + } + + @Test + void testReactivateUserAllResponseBodyProperties() throws SmartsheetException { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/users/reactivate-user/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + Assertions.assertDoesNotThrow(() -> { + smartsheet.userResources().reactivateUser(TEST_USER_ID); + }); + } + + @Test + void testReactivateUserError500Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/500-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.userResources().reactivateUser(TEST_USER_ID); + }); + + assertThat(exception.getMessage()).isEqualTo("Internal Server Error"); + } + + @Test + void testReactivateUserError400Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/400-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.userResources().reactivateUser(TEST_USER_ID); + }); + + assertThat(exception.getMessage()).isEqualTo("Malformed Request"); + } +} From d3f4f2537931827550721826f97b5d001957223d Mon Sep 17 00:00:00 2001 From: denislavstanchev Date: Thu, 4 Dec 2025 13:58:17 +0200 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5fd78ac..f28b45c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add provisionalExpirationDate field to the User model - Add provisionalExpirationDate field to the UserPlan model - Remove integration tests from the sdk test suite and workflows +- Support for POST /2.0/users/{userId}/reactivate endpoint +- Support for POST /2.0/users/{userId}/deactivate endpoint ### Updated - Folder structure for the Users related WireMock tests