diff --git a/CHANGELOG.md b/CHANGELOG.md index cf40d341..e61ca2a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [x.x.x] - unreleased +- Support for the POST /2.0/reports/{id}/scope endpoint +- Support for the DELETE /2.0/reports/{id}/scope endpoint +- WireMock integration tests for contract testing for POST /2.0/reports/{id}/scope and DELETE /2.0/reports/{id}/scope endpoints ## [3.10.0] - 2025-12-04 ### Added diff --git a/src/main/java/com/smartsheet/api/ReportResources.java b/src/main/java/com/smartsheet/api/ReportResources.java index 973ed703..d5c6c647 100644 --- a/src/main/java/com/smartsheet/api/ReportResources.java +++ b/src/main/java/com/smartsheet/api/ReportResources.java @@ -20,12 +20,14 @@ import com.smartsheet.api.models.PaginationParameters; import com.smartsheet.api.models.Report; import com.smartsheet.api.models.ReportPublish; +import com.smartsheet.api.models.ReportScopeInclusion; import com.smartsheet.api.models.SheetEmail; import com.smartsheet.api.models.enums.ReportInclusion; import java.io.OutputStream; import java.util.Date; import java.util.EnumSet; +import java.util.List; /** *

This interface provides methods to access Report resources.

@@ -218,4 +220,36 @@ Report getReport( * @return the created ShareResources object */ ShareResources shareResources(); + + /** + *

Adds one or more specified sheet or workspace to the report scope.

+ * + * @param id the ID of the report + * @param scopes A list of one or more objects denoting the sheets or workspaces associated with + * the report to be added to the report scope. + * + * @throws IllegalArgumentException if any argument is null or empty string + * @throws InvalidRequestException if there is any problem with the REST API request + * @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 addReportScope(long id, List scopes) throws SmartsheetException; + + /** + *

Removes one or more specified sheet or workspace from the report scope.

+ * + * @param id the ID of the report + * @param scopes A list of one or more objects denoting the sheets or workspaces associated with + * the report to be removed from the report scope. + * + * @throws IllegalArgumentException if any argument is null or empty string + * @throws InvalidRequestException if there is any problem with the REST API request + * @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 removeReportScope(long id, List scopes) throws SmartsheetException; } diff --git a/src/main/java/com/smartsheet/api/internal/ReportResourcesImpl.java b/src/main/java/com/smartsheet/api/internal/ReportResourcesImpl.java index 1951411c..89f668a4 100644 --- a/src/main/java/com/smartsheet/api/internal/ReportResourcesImpl.java +++ b/src/main/java/com/smartsheet/api/internal/ReportResourcesImpl.java @@ -23,20 +23,30 @@ import com.smartsheet.api.ServiceUnavailableException; import com.smartsheet.api.ShareResources; import com.smartsheet.api.SmartsheetException; +import com.smartsheet.api.internal.http.HttpEntity; +import com.smartsheet.api.internal.http.HttpMethod; +import com.smartsheet.api.internal.http.HttpRequest; +import com.smartsheet.api.internal.http.HttpResponse; +import com.smartsheet.api.internal.json.JSONSerializerException; import com.smartsheet.api.internal.util.QueryUtil; import com.smartsheet.api.models.PagedResult; import com.smartsheet.api.models.PaginationParameters; import com.smartsheet.api.models.Report; import com.smartsheet.api.models.ReportPublish; +import com.smartsheet.api.models.ReportScopeInclusion; import com.smartsheet.api.models.SheetEmail; +import com.smartsheet.api.internal.util.Util; import com.smartsheet.api.models.enums.ReportInclusion; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.List; /** * This is the implementation of the ReportResources. @@ -53,6 +63,7 @@ public class ReportResourcesImpl extends AbstractResources implements ReportReso */ private ShareResources shares; + private static final String JSON_CONTENT_TYPE = "application/json"; private static final String REPORTS_PATH = "reports/"; /** @@ -313,4 +324,84 @@ public ReportPublish updatePublishStatus(long id, ReportPublish reportPublish) t public ShareResources shareResources() { return this.shares; } + + /** + *

Adds one or more specified sheet or workspace to the report scope.

+ * + * @param id the ID of the report + * @param scopes A list of one or more objects denoting the sheets or workspaces associated with + * the report to be added to the report scope. + * @throws IllegalArgumentException if any argument is null or empty + * @throws InvalidRequestException if there is any problem with the REST API request + * @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 addReportScope(long id, List scopes) throws SmartsheetException { + Util.throwIfNull(scopes); + + if (scopes.isEmpty()) { + throw new IllegalArgumentException("scopes should not be empty."); + } + + String path = REPORTS_PATH + id + "/scope"; + HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.POST); + setRequestEntity(request, scopes); + + try { + HttpResponse response = this.smartsheet.getHttpClient().request(request); + if (response.getStatusCode() != 200) { + handleError(response); + } + } finally { + smartsheet.getHttpClient().releaseConnection(); + } + } + + /** + *

Removes one or more specified sheet or workspace from the report scope.

+ * + * @param id the ID of the report + * @param scopes A list of one or more objects denoting the sheets or workspaces associated with + * the report to be removed from the report scope. + * @throws IllegalArgumentException if any argument is null or empty + * @throws InvalidRequestException if there is any problem with the REST API request + * @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 removeReportScope(long id, List scopes) throws SmartsheetException { + Util.throwIfNull(scopes); + + if (scopes.isEmpty()) { + throw new IllegalArgumentException("scopes should not be empty."); + } + + String path = REPORTS_PATH + id + "/scope"; + HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.DELETE); + setRequestEntity(request, scopes); + + try { + HttpResponse response = this.smartsheet.getHttpClient().request(request); + if (response.getStatusCode() != 200) { + handleError(response); + } + } finally { + smartsheet.getHttpClient().releaseConnection(); + } + } + + private void setRequestEntity(HttpRequest request, Object object) throws JSONSerializerException { + ByteArrayOutputStream objectBytesStream = new ByteArrayOutputStream(); + this.smartsheet.getJsonSerializer().serialize(object, objectBytesStream); + HttpEntity entity = new HttpEntity(); + entity.setContentType(JSON_CONTENT_TYPE); + entity.setContent(new ByteArrayInputStream(objectBytesStream.toByteArray())); + entity.setContentLength(objectBytesStream.size()); + request.setEntity(entity); + } } diff --git a/src/main/java/com/smartsheet/api/models/ReportScopeInclusion.java b/src/main/java/com/smartsheet/api/models/ReportScopeInclusion.java new file mode 100644 index 00000000..fdd29b4f --- /dev/null +++ b/src/main/java/com/smartsheet/api/models/ReportScopeInclusion.java @@ -0,0 +1,66 @@ +/* + * 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.models; + +import com.smartsheet.api.models.enums.ReportAssetType; + +/** + * Represents the ReportScopeInclusion object used when adding and removing report scopes. + */ +public class ReportScopeInclusion { + /** + * The type of asset that is included in the report. + */ + private ReportAssetType assetType; + + /** + * The id of the asset that is included in the report. + */ + private Long assetId; + + /** + * Get the type of asset that is included in the report. + * @return the type of asset that is included in the report + */ + public ReportAssetType getAssetType() { + return assetType; + } + + /** + * Set the type of asset that is included in the report. + * @param assetType the type of asset that is included in the report + */ + public void setAssetType(ReportAssetType assetType) { + this.assetType = assetType; + } + + /** + * Get the id of the asset that is included in the report. + * @return the id of the asset that is included in the report + */ + public Long getAssetId() { + return assetId; + } + + /** + * Set the id of the asset that is included in the report. + * @param assetId the id of the asset that is included in the report + */ + public void setAssetId(Long assetId) { + this.assetId = assetId; + } +} diff --git a/src/main/java/com/smartsheet/api/models/enums/ReportAssetType.java b/src/main/java/com/smartsheet/api/models/enums/ReportAssetType.java new file mode 100644 index 00000000..a29f4250 --- /dev/null +++ b/src/main/java/com/smartsheet/api/models/enums/ReportAssetType.java @@ -0,0 +1,32 @@ +/* + * 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.models.enums; + +/** + * Represents the type of asset that is included in the report. + */ +public enum ReportAssetType { + /** + * A sheet that is included in the report. + */ + SHEET, + + /** + * A workspace that is included in the report. + */ + WORKSPACE +} diff --git a/src/test/java/com/smartsheet/api/sdktest/reports/TestAddReportScope.java b/src/test/java/com/smartsheet/api/sdktest/reports/TestAddReportScope.java new file mode 100644 index 00000000..62c05e30 --- /dev/null +++ b/src/test/java/com/smartsheet/api/sdktest/reports/TestAddReportScope.java @@ -0,0 +1,131 @@ +/* + * 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.reports; + +import com.github.tomakehurst.wiremock.http.RequestMethod; +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 com.smartsheet.api.models.ReportScopeInclusion; +import com.smartsheet.api.models.enums.ReportAssetType; +import com.smartsheet.api.sdktest.users.Utils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static com.smartsheet.api.sdktest.users.CommonTestConstants.TEST_REPORT_ID; +import static com.smartsheet.api.sdktest.users.CommonTestConstants.TEST_SHEET_ID; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestAddReportScope { + + private List testScopes; + + @BeforeEach + void setUp() { + ReportScopeInclusion scope = new ReportScopeInclusion(); + scope.setAssetType(ReportAssetType.SHEET); + scope.setAssetId(TEST_SHEET_ID); + + testScopes = new ArrayList<>(); + testScopes.add(scope); + } + + @Test + void testAddReportScopeGeneratedUrlIsCorrect() throws SmartsheetException { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/reports/add-report-scope/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + WiremockClient wiremockClient = wrapper.getWiremockClient(); + + smartsheet.reportResources().addReportScope(TEST_REPORT_ID, testScopes); + LoggedRequest wiremockRequest = wiremockClient.findWiremockRequest(requestId); + String path = URI.create(wiremockRequest.getUrl()).getPath(); + + assertThat(path).isEqualTo("/2.0/reports/" + TEST_REPORT_ID + "/scope"); + assertThat(wiremockRequest.getMethod()).isEqualTo(RequestMethod.POST); + } + + @Test + void testAddReportScopeAllResponseBodyProperties() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/reports/add-report-scope/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + Assertions.assertDoesNotThrow(() -> { + smartsheet.reportResources().addReportScope(TEST_REPORT_ID, testScopes); + }); + } + + @Test + void testAddReportScopeInvalidArgument() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/reports/add-report-scope/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + smartsheet.reportResources().addReportScope(TEST_REPORT_ID, null); + }); + + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { + smartsheet.reportResources().addReportScope(TEST_REPORT_ID, new ArrayList<>()); + }); + assertThat(exception.getMessage()).isEqualTo("scopes should not be empty."); + } + + @Test + void testAddReportScopeError500Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/500-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.reportResources().addReportScope(TEST_REPORT_ID, testScopes); + }); + + assertThat(exception.getMessage()).isEqualTo("Internal Server Error"); + } + + @Test + void testAddReportScopeError400Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/400-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.reportResources().addReportScope(TEST_REPORT_ID, testScopes); + }); + + assertThat(exception.getMessage()).isEqualTo("Malformed Request"); + } +} diff --git a/src/test/java/com/smartsheet/api/sdktest/reports/TestRemoveReportScope.java b/src/test/java/com/smartsheet/api/sdktest/reports/TestRemoveReportScope.java new file mode 100644 index 00000000..1e06b0e2 --- /dev/null +++ b/src/test/java/com/smartsheet/api/sdktest/reports/TestRemoveReportScope.java @@ -0,0 +1,131 @@ +/* + * 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.reports; + +import com.github.tomakehurst.wiremock.http.RequestMethod; +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 com.smartsheet.api.models.ReportScopeInclusion; +import com.smartsheet.api.models.enums.ReportAssetType; +import com.smartsheet.api.sdktest.users.Utils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static com.smartsheet.api.sdktest.users.CommonTestConstants.TEST_REPORT_ID; +import static com.smartsheet.api.sdktest.users.CommonTestConstants.TEST_SHEET_ID; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestRemoveReportScope { + + private List testScopes; + + @BeforeEach + void setUp() { + ReportScopeInclusion scope = new ReportScopeInclusion(); + scope.setAssetType(ReportAssetType.SHEET); + scope.setAssetId(TEST_SHEET_ID); + + testScopes = new ArrayList<>(); + testScopes.add(scope); + } + + @Test + void testRemoveReportScopeGeneratedUrlIsCorrect() throws SmartsheetException { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/reports/remove-report-scope/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + WiremockClient wiremockClient = wrapper.getWiremockClient(); + + smartsheet.reportResources().removeReportScope(TEST_REPORT_ID, testScopes); + LoggedRequest wiremockRequest = wiremockClient.findWiremockRequest(requestId); + String path = URI.create(wiremockRequest.getUrl()).getPath(); + + assertThat(path).isEqualTo("/2.0/reports/" + TEST_REPORT_ID + "/scope"); + assertThat(wiremockRequest.getMethod()).isEqualTo(RequestMethod.DELETE); + } + + @Test + void testRemoveReportScopeAllResponseBodyProperties() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/reports/remove-report-scope/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + Assertions.assertDoesNotThrow(() -> { + smartsheet.reportResources().removeReportScope(TEST_REPORT_ID, testScopes); + }); + } + + @Test + void testRemoveReportScopeInvalidArgument() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient( + "/reports/remove-report-scope/all-response-body-properties", + requestId + ); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + smartsheet.reportResources().removeReportScope(TEST_REPORT_ID, null); + }); + + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { + smartsheet.reportResources().removeReportScope(TEST_REPORT_ID, new ArrayList<>()); + }); + assertThat(exception.getMessage()).isEqualTo("scopes should not be empty."); + } + + @Test + void testRemoveReportScopeError500Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/500-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.reportResources().removeReportScope(TEST_REPORT_ID, testScopes); + }); + + assertThat(exception.getMessage()).isEqualTo("Internal Server Error"); + } + + @Test + void testRemoveReportScopeError400Response() { + String requestId = UUID.randomUUID().toString(); + WiremockClientWrapper wrapper = Utils.createWiremockSmartsheetClient("/errors/400-response", requestId); + Smartsheet smartsheet = wrapper.getSmartsheet(); + + SmartsheetException exception = Assertions.assertThrows(SmartsheetException.class, () -> { + smartsheet.reportResources().removeReportScope(TEST_REPORT_ID, testScopes); + }); + + assertThat(exception.getMessage()).isEqualTo("Malformed Request"); + } +} diff --git a/src/test/java/com/smartsheet/api/sdktest/users/CommonTestConstants.java b/src/test/java/com/smartsheet/api/sdktest/users/CommonTestConstants.java index 400d31b6..c889cb9b 100644 --- a/src/test/java/com/smartsheet/api/sdktest/users/CommonTestConstants.java +++ b/src/test/java/com/smartsheet/api/sdktest/users/CommonTestConstants.java @@ -19,4 +19,7 @@ public class CommonTestConstants { public static final long TEST_USER_ID = 1234567890L; public static final long TEST_PLAN_ID = 1234567890123456L; + public static final long TEST_SHEET_ID = 9876543210L; + public static final long TEST_WORKSPACE_ID = 1122334455L; + public static final long TEST_REPORT_ID = 2233445566L; }