From 3d9b28373fb62ca2ef0bc9cec6d5a848dfaa40d7 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 11 Sep 2025 14:57:15 +0100 Subject: [PATCH 01/16] Stash: updateVersionLicense endpoint WIP --- .../harvard/iq/dataverse/api/Datasets.java | 15 ++++++++++++- .../UpdateDatasetVersionLicenseCommand.java | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index fb97c761ef1..abedba4bcaa 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -6057,7 +6057,7 @@ public Response deleteDatasetFiles(@Context ContainerRequestContext crc, @PathPa }, getRequestUser(crc)); } -@GET + @GET @AuthRequired @Path("{id}/versions/{versionId}/versionNote") public Response getVersionCreationNote(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers) throws WrappedResponse { @@ -6127,4 +6127,17 @@ public Response deleteVersionNote(@Context ContainerRequestContext crc, @PathPar }, getRequestUser(crc)); } + @PUT + @AuthRequired + @Path("{id}/versions/{versionId}/license") + public Response updateVersionLicense(@Context ContainerRequestContext crc, + @PathParam("id") String datasetId, + @PathParam("versionId") String versionId, + @Context UriInfo uriInfo, + @Context HttpHeaders headers) { + return response( req -> { + DatasetVersion datasetVersion = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers); + return ok("TODO"); + }, getRequestUser(crc)); + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java new file mode 100644 index 00000000000..dc1c2e2b491 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java @@ -0,0 +1,21 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.DatasetVersion; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.engine.command.*; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; + +@RequiredPermissions(Permission.EditDataset) +public class UpdateDatasetVersionLicenseCommand extends AbstractVoidCommand { + private final DatasetVersion datasetVersion; + + public UpdateDatasetVersionLicenseCommand(DataverseRequest aRequest, DatasetVersion datasetVersion) { + super(aRequest, datasetVersion.getDataset()); + this.datasetVersion = datasetVersion; + } + + @Override + protected void executeImpl(CommandContext ctxt) throws CommandException { + + } +} From 817ddafc004094a48bcd17878947160d578cbf2e Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Sep 2025 10:06:23 +0100 Subject: [PATCH 02/16] Stash: updateVersionLicense endpoint WIP. Endpoint logic implemented, pending command impl --- .../edu/harvard/iq/dataverse/api/Datasets.java | 14 ++++++++------ .../impl/UpdateDatasetVersionLicenseCommand.java | 4 +++- src/main/java/propertyFiles/Bundle.properties | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index abedba4bcaa..8d50c1079f8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -6131,13 +6131,15 @@ public Response deleteVersionNote(@Context ContainerRequestContext crc, @PathPar @AuthRequired @Path("{id}/versions/{versionId}/license") public Response updateVersionLicense(@Context ContainerRequestContext crc, - @PathParam("id") String datasetId, - @PathParam("versionId") String versionId, - @Context UriInfo uriInfo, - @Context HttpHeaders headers) { - return response( req -> { + @PathParam("id") String datasetId, + @PathParam("versionId") String versionId, + String licenseName, + @Context UriInfo uriInfo, + @Context HttpHeaders headers) { + return response(req -> { DatasetVersion datasetVersion = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers); - return ok("TODO"); + execCommand(new UpdateDatasetVersionLicenseCommand(req, datasetVersion, licenseName)); + return ok(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success")); }, getRequestUser(crc)); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java index dc1c2e2b491..61875adafc0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java @@ -8,10 +8,12 @@ @RequiredPermissions(Permission.EditDataset) public class UpdateDatasetVersionLicenseCommand extends AbstractVoidCommand { private final DatasetVersion datasetVersion; + private final String licenseName; - public UpdateDatasetVersionLicenseCommand(DataverseRequest aRequest, DatasetVersion datasetVersion) { + public UpdateDatasetVersionLicenseCommand(DataverseRequest aRequest, DatasetVersion datasetVersion, String licenseName) { super(aRequest, datasetVersion.getDataset()); this.datasetVersion = datasetVersion; + this.licenseName = licenseName; } @Override diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index a07546284e0..24b60d45d8d 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -2856,6 +2856,7 @@ datasets.api.thumbnail.fileNotSupplied=A file was not selected to be the new dat datasets.api.thumbnail.noChange=No changes to save. datasets.api.editMetadata.error.parseUpdate=Error parsing dataset update: {0} datasets.api.citation.invalidFormat=Invalid Format Requested. +datasets.api.updateLicense.success=Dataset version license updated. #Dataverses.java dataverses.api.update.default.contributor.role.failure.role.not.found=Role {0} not found. From 1ad96d3684a4d39a3deecca0bfaa3e400a561452 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Sep 2025 10:08:34 +0100 Subject: [PATCH 03/16] Refactor: removed unused injected services in Datasets.java --- src/main/java/edu/harvard/iq/dataverse/api/Datasets.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 8d50c1079f8..44633161e21 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -28,7 +28,6 @@ import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.exception.*; import edu.harvard.iq.dataverse.engine.command.impl.*; -import edu.harvard.iq.dataverse.export.DDIExportServiceBean; import edu.harvard.iq.dataverse.export.ExportService; import edu.harvard.iq.dataverse.externaltools.ExternalTool; import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler; @@ -139,9 +138,6 @@ public class Datasets extends AbstractApiBean { @EJB AuthenticationServiceBean authenticationServiceBean; - @EJB - DDIExportServiceBean ddiExportService; - @EJB MetadataBlockServiceBean metadataBlockService; @@ -200,9 +196,6 @@ public class Datasets extends AbstractApiBean { @Inject DatasetTypeServiceBean datasetTypeSvc; - @Inject - DatasetFieldsValidator datasetFieldsValidator; - @Inject DataFileCategoryServiceBean dataFileCategoryService; From f33028ed64b92932c8aedbdf657682b831a41779 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Sep 2025 10:12:15 +0100 Subject: [PATCH 04/16] Refactor: pending TODO refactor implemented. datasetMetricsService moved to AbstractApiBean --- .../edu/harvard/iq/dataverse/api/AbstractApiBean.java | 4 ++++ .../java/edu/harvard/iq/dataverse/api/Datasets.java | 6 +----- .../edu/harvard/iq/dataverse/api/MakeDataCountApi.java | 10 ---------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 46e8263da15..bbc18f0757b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -23,6 +23,7 @@ import edu.harvard.iq.dataverse.engine.command.impl.GetSpecificPublishedDatasetVersionCommand; import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean; import edu.harvard.iq.dataverse.license.LicenseServiceBean; +import edu.harvard.iq.dataverse.makedatacount.DatasetMetricsServiceBean; import edu.harvard.iq.dataverse.pidproviders.FailedPIDResolutionLoggingServiceBean; import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.pidproviders.FailedPIDResolutionLoggingServiceBean.FailedPIDResolutionEntry; @@ -222,6 +223,9 @@ String getWrappedMessageWhenJson() { @EJB protected ExternalToolServiceBean externalToolService; + @EJB + protected DatasetMetricsServiceBean datasetMetricsService; + @EJB DataFileServiceBean fileSvc; diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 44633161e21..8c03d074961 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -159,10 +159,6 @@ public class Datasets extends AbstractApiBean { @EJB SettingsServiceBean settingsService; - // TODO: Move to AbstractApiBean - @EJB - DatasetMetricsServiceBean datasetMetricsSvc; - @EJB DatasetExternalCitationsServiceBean datasetExternalCitationsService; @@ -3584,7 +3580,7 @@ public Response getMakeDataCountMetric(@PathParam("id") String idSupplied, @Path return error(Response.Status.BAD_REQUEST, "Country must be one of the ISO 1366 Country Codes"); } } - DatasetMetrics datasetMetrics = datasetMetricsSvc.getDatasetMetricsByDatasetForDisplay(dataset, monthYear, country); + DatasetMetrics datasetMetrics = datasetMetricsService.getDatasetMetricsByDatasetForDisplay(dataset, monthYear, country); if (datasetMetrics == null) { return ok("No metrics available for dataset " + dataset.getId() + " for " + yyyymm + " for country code " + country + "."); } else if (datasetMetrics.getDownloadsTotal() + datasetMetrics.getViewsTotal() == 0) { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java b/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java index 562fd7fcb81..e3ff8c9da89 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java @@ -1,25 +1,21 @@ package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.Dataset; -import edu.harvard.iq.dataverse.DatasetServiceBean; import edu.harvard.iq.dataverse.GlobalId; import edu.harvard.iq.dataverse.makedatacount.DatasetExternalCitations; import edu.harvard.iq.dataverse.makedatacount.DatasetExternalCitationsServiceBean; import edu.harvard.iq.dataverse.makedatacount.DatasetMetrics; -import edu.harvard.iq.dataverse.makedatacount.DatasetMetricsServiceBean; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountProcessState; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountProcessStateServiceBean; import edu.harvard.iq.dataverse.pidproviders.PidProvider; import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.pidproviders.doi.datacite.DataCiteDOIProvider; import edu.harvard.iq.dataverse.settings.JvmSettings; -import edu.harvard.iq.dataverse.util.SystemConfig; import edu.harvard.iq.dataverse.util.json.JsonUtil; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -51,16 +47,10 @@ public class MakeDataCountApi extends AbstractApiBean { private static final Logger logger = Logger.getLogger(MakeDataCountApi.class.getCanonicalName()); - @EJB - DatasetMetricsServiceBean datasetMetricsService; @EJB MakeDataCountProcessStateServiceBean makeDataCountProcessStateService; @EJB DatasetExternalCitationsServiceBean datasetExternalCitationsService; - @EJB - DatasetServiceBean datasetService; - @EJB - SystemConfig systemConfig; /** * TODO: For each dataset, send the following: From ae10ceefd6d754cb385d353dbfa352401544f07f Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Sep 2025 13:32:15 +0100 Subject: [PATCH 05/16] Changed: license update implementation with unit tests --- .../harvard/iq/dataverse/api/Datasets.java | 18 ++-- .../UpdateDatasetVersionLicenseCommand.java | 30 +++++-- src/main/java/propertyFiles/Bundle.properties | 7 +- ...pdateDatasetVersionLicenseCommandTest.java | 88 +++++++++++++++++++ 4 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 8c03d074961..384d7509803 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -6118,16 +6118,20 @@ public Response deleteVersionNote(@Context ContainerRequestContext crc, @PathPar @PUT @AuthRequired - @Path("{id}/versions/{versionId}/license") + @Path("{id}/license") public Response updateVersionLicense(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, - @PathParam("versionId") String versionId, - String licenseName, - @Context UriInfo uriInfo, - @Context HttpHeaders headers) { + String licenseName) { return response(req -> { - DatasetVersion datasetVersion = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers); - execCommand(new UpdateDatasetVersionLicenseCommand(req, datasetVersion, licenseName)); + if (licenseName.isEmpty()) { + return badRequest(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNameIsEmpty")); + } + Dataset dataset = findDatasetOrDie(datasetId); + License license = licenseSvc.getByNameOrUri(licenseName); + if (license == null) { + return notFound(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNotFound", List.of(licenseName))); + } + execCommand(new UpdateDatasetVersionLicenseCommand(req, dataset, license)); return ok(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success")); }, getRequestUser(crc)); } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java index 61875adafc0..cd8c0c1431d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java @@ -1,23 +1,41 @@ package edu.harvard.iq.dataverse.engine.command.impl; +import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetVersion; +import edu.harvard.iq.dataverse.TermsOfUseAndAccess; import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.engine.command.*; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.InvalidCommandArgumentsException; +import edu.harvard.iq.dataverse.license.License; +import edu.harvard.iq.dataverse.util.BundleUtil; + +import java.util.List; @RequiredPermissions(Permission.EditDataset) public class UpdateDatasetVersionLicenseCommand extends AbstractVoidCommand { - private final DatasetVersion datasetVersion; - private final String licenseName; + private final Dataset dataset; + private final License license; - public UpdateDatasetVersionLicenseCommand(DataverseRequest aRequest, DatasetVersion datasetVersion, String licenseName) { - super(aRequest, datasetVersion.getDataset()); - this.datasetVersion = datasetVersion; - this.licenseName = licenseName; + public UpdateDatasetVersionLicenseCommand(DataverseRequest aRequest, Dataset dataset, License license) { + super(aRequest, dataset); + this.dataset = dataset; + this.license = license; } @Override protected void executeImpl(CommandContext ctxt) throws CommandException { + if (!license.isActive()) { + throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetVersionLicenseCommand.errors.licenseNotActive", List.of(license.getName())), this); + } + + DatasetVersion datasetVersion = dataset.getOrCreateEditVersion(); + + TermsOfUseAndAccess termsOfUseAndAccess = datasetVersion.getTermsOfUseAndAccess(); + termsOfUseAndAccess.setLicense(license); + + datasetVersion.setVersionState(DatasetVersion.VersionState.DRAFT); + ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); } } diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 24b60d45d8d..3f0c94694a8 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -2856,7 +2856,9 @@ datasets.api.thumbnail.fileNotSupplied=A file was not selected to be the new dat datasets.api.thumbnail.noChange=No changes to save. datasets.api.editMetadata.error.parseUpdate=Error parsing dataset update: {0} datasets.api.citation.invalidFormat=Invalid Format Requested. -datasets.api.updateLicense.success=Dataset version license updated. +datasets.api.updateLicense.success=Dataset license updated. +datasets.api.updateLicense.licenseNotFound=License with name {0} not found. +datasets.api.updateLicense.licenseNameIsEmpty=License name cannot be empty. #Dataverses.java dataverses.api.update.default.contributor.role.failure.role.not.found=Role {0} not found. @@ -3237,3 +3239,6 @@ roleAssigneeServiceBean.error.dataverseRequestCannotBeNull=DataverseRequest cann #GetUserPermittedCollectionsCommand.java getUserPermittedCollectionsCommand.errors.userNotFound=User not found. getUserPermittedCollectionsCommand.errors.permissionNotValid=Permission not valid. + +#UpdateDatasetVersionLicenseCommand.java +updateDatasetVersionLicenseCommand.errors.licenseNotActive=License {0} cannot be set because it is not active. diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java new file mode 100644 index 00000000000..bf0cb70af8b --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java @@ -0,0 +1,88 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.Dataset; +import edu.harvard.iq.dataverse.DatasetVersion; +import edu.harvard.iq.dataverse.TermsOfUseAndAccess; +import edu.harvard.iq.dataverse.engine.DataverseEngine; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.InvalidCommandArgumentsException; +import edu.harvard.iq.dataverse.license.License; +import edu.harvard.iq.dataverse.util.BundleUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class UpdateDatasetVersionLicenseCommandTest { + + @Mock + private DataverseRequest dataverseRequestStub; + @Mock + private UpdateDatasetVersionCommand updateDatasetVersionCommandStub; + @Mock + private Dataset datasetMock; + @Mock + private DatasetVersion datasetVersionMock; + @Spy + private TermsOfUseAndAccess termsOfUseAndAccessSpy = new TermsOfUseAndAccess(); + @Mock + private DataverseEngine dataverseEngineMock; + @Mock + private CommandContext commandContextMock; + + private License activeLicense; + private License inactiveLicense; + + @BeforeEach + public void setUp() throws CommandException { + MockitoAnnotations.openMocks(this); + + when(datasetMock.getOrCreateEditVersion()).thenReturn(datasetVersionMock); + when(datasetVersionMock.getTermsOfUseAndAccess()).thenReturn(termsOfUseAndAccessSpy); + when(dataverseEngineMock.submit(updateDatasetVersionCommandStub)).thenReturn(datasetMock); + when(commandContextMock.engine()).thenReturn(dataverseEngineMock); + + activeLicense = new License(); + activeLicense.setActive(true); + activeLicense.setName("activeLicense"); + + inactiveLicense = new License(); + inactiveLicense.setActive(false); + inactiveLicense.setName("inactiveLicense"); + } + + @Test + public void execute_shouldUpdateLicenseAndSetVersionStateToDraft() throws CommandException { + // Arrange + UpdateDatasetVersionLicenseCommand sut = new UpdateDatasetVersionLicenseCommand(dataverseRequestStub, datasetMock, activeLicense); + + // Act + sut.execute(commandContextMock); + + // Assert + assertEquals(activeLicense, termsOfUseAndAccessSpy.getLicense()); + verify(datasetVersionMock).setVersionState(DatasetVersion.VersionState.DRAFT); + verify(commandContextMock).engine(); + } + + @Test + public void execute_shouldThrowException_whenLicenseIsNotActive() { + // Arrange + UpdateDatasetVersionLicenseCommand sut = new UpdateDatasetVersionLicenseCommand(dataverseRequestStub, datasetMock, inactiveLicense); + String expectedMessage = BundleUtil.getStringFromBundle("updateDatasetVersionLicenseCommand.errors.licenseNotActive", List.of(inactiveLicense.getName())); + + // Act & Assert + InvalidCommandArgumentsException exception = assertThrows(InvalidCommandArgumentsException.class, () -> sut.execute(commandContextMock)); + assertEquals(expectedMessage, exception.getMessage()); + } +} From 5f33be4308f3b7ef64c95e01db26df9c919a1650 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Sep 2025 13:33:46 +0100 Subject: [PATCH 06/16] Refactor: renamed UpdateDatasetVersionLicenseCommand to UpdateDatasetLicenseCommand --- src/main/java/edu/harvard/iq/dataverse/api/Datasets.java | 2 +- ...LicenseCommand.java => UpdateDatasetLicenseCommand.java} | 4 ++-- ...ommandTest.java => UpdateDatasetLicenseCommandTest.java} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/edu/harvard/iq/dataverse/engine/command/impl/{UpdateDatasetVersionLicenseCommand.java => UpdateDatasetLicenseCommand.java} (89%) rename src/test/java/edu/harvard/iq/dataverse/engine/command/impl/{UpdateDatasetVersionLicenseCommandTest.java => UpdateDatasetLicenseCommandTest.java} (90%) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 384d7509803..3efad2606bc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -6131,7 +6131,7 @@ public Response updateVersionLicense(@Context ContainerRequestContext crc, if (license == null) { return notFound(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNotFound", List.of(licenseName))); } - execCommand(new UpdateDatasetVersionLicenseCommand(req, dataset, license)); + execCommand(new UpdateDatasetLicenseCommand(req, dataset, license)); return ok(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success")); }, getRequestUser(crc)); } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java similarity index 89% rename from src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java rename to src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java index cd8c0c1431d..1a89b21739a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java @@ -13,11 +13,11 @@ import java.util.List; @RequiredPermissions(Permission.EditDataset) -public class UpdateDatasetVersionLicenseCommand extends AbstractVoidCommand { +public class UpdateDatasetLicenseCommand extends AbstractVoidCommand { private final Dataset dataset; private final License license; - public UpdateDatasetVersionLicenseCommand(DataverseRequest aRequest, Dataset dataset, License license) { + public UpdateDatasetLicenseCommand(DataverseRequest aRequest, Dataset dataset, License license) { super(aRequest, dataset); this.dataset = dataset; this.license = license; diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java similarity index 90% rename from src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java rename to src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java index bf0cb70af8b..701e01f2713 100644 --- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetVersionLicenseCommandTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class UpdateDatasetVersionLicenseCommandTest { +public class UpdateDatasetLicenseCommandTest { @Mock private DataverseRequest dataverseRequestStub; @@ -64,7 +64,7 @@ public void setUp() throws CommandException { @Test public void execute_shouldUpdateLicenseAndSetVersionStateToDraft() throws CommandException { // Arrange - UpdateDatasetVersionLicenseCommand sut = new UpdateDatasetVersionLicenseCommand(dataverseRequestStub, datasetMock, activeLicense); + UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, activeLicense); // Act sut.execute(commandContextMock); @@ -78,7 +78,7 @@ public void execute_shouldUpdateLicenseAndSetVersionStateToDraft() throws Comman @Test public void execute_shouldThrowException_whenLicenseIsNotActive() { // Arrange - UpdateDatasetVersionLicenseCommand sut = new UpdateDatasetVersionLicenseCommand(dataverseRequestStub, datasetMock, inactiveLicense); + UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, inactiveLicense); String expectedMessage = BundleUtil.getStringFromBundle("updateDatasetVersionLicenseCommand.errors.licenseNotActive", List.of(inactiveLicense.getName())); // Act & Assert From e253f22b9864bb51fe7fdc4a6488434b1d518cb6 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Sep 2025 13:40:06 +0100 Subject: [PATCH 07/16] Changed: renamed bundle string --- .../engine/command/impl/UpdateDatasetLicenseCommand.java | 2 +- src/main/java/propertyFiles/Bundle.properties | 4 ++-- .../engine/command/impl/UpdateDatasetLicenseCommandTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java index 1a89b21739a..dcc08d38935 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java @@ -26,7 +26,7 @@ public UpdateDatasetLicenseCommand(DataverseRequest aRequest, Dataset dataset, L @Override protected void executeImpl(CommandContext ctxt) throws CommandException { if (!license.isActive()) { - throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetVersionLicenseCommand.errors.licenseNotActive", List.of(license.getName())), this); + throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.licenseNotActive", List.of(license.getName())), this); } DatasetVersion datasetVersion = dataset.getOrCreateEditVersion(); diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 3f0c94694a8..9c2cd88e6f3 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -3240,5 +3240,5 @@ roleAssigneeServiceBean.error.dataverseRequestCannotBeNull=DataverseRequest cann getUserPermittedCollectionsCommand.errors.userNotFound=User not found. getUserPermittedCollectionsCommand.errors.permissionNotValid=Permission not valid. -#UpdateDatasetVersionLicenseCommand.java -updateDatasetVersionLicenseCommand.errors.licenseNotActive=License {0} cannot be set because it is not active. +#UpdateDatasetLicenseCommand.java +updateDatasetLicenseCommand.errors.licenseNotActive=License {0} cannot be set because it is not active. diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java index 701e01f2713..896f24d7ac7 100644 --- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java @@ -79,7 +79,7 @@ public void execute_shouldUpdateLicenseAndSetVersionStateToDraft() throws Comman public void execute_shouldThrowException_whenLicenseIsNotActive() { // Arrange UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, inactiveLicense); - String expectedMessage = BundleUtil.getStringFromBundle("updateDatasetVersionLicenseCommand.errors.licenseNotActive", List.of(inactiveLicense.getName())); + String expectedMessage = BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.licenseNotActive", List.of(inactiveLicense.getName())); // Act & Assert InvalidCommandArgumentsException exception = assertThrows(InvalidCommandArgumentsException.class, () -> sut.execute(commandContextMock)); From 6fbd2d9bbb09693529ffb7f228d91940741e58ac Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Sep 2025 13:53:44 +0100 Subject: [PATCH 08/16] Added: handling license updates where custom terms are sent --- .../harvard/iq/dataverse/api/Datasets.java | 26 +++-- .../iq/dataverse/api/dto/CustomTermsDTO.java | 94 +++++++++++++++++++ .../api/dto/LicenseUpdateRequest.java | 30 ++++++ .../impl/UpdateDatasetLicenseCommand.java | 30 ++++-- .../impl/UpdateDatasetLicenseCommandTest.java | 16 ++++ 5 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 3efad2606bc..420ebcbe0b7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -5,6 +5,8 @@ import edu.harvard.iq.dataverse.DatasetVersion.VersionState; import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord; import edu.harvard.iq.dataverse.api.auth.AuthRequired; +import edu.harvard.iq.dataverse.api.dto.CustomTermsDTO; +import edu.harvard.iq.dataverse.api.dto.LicenseUpdateRequest; import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO; import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.DataverseRole; @@ -6121,18 +6123,24 @@ public Response deleteVersionNote(@Context ContainerRequestContext crc, @PathPar @Path("{id}/license") public Response updateVersionLicense(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, - String licenseName) { + LicenseUpdateRequest requestBody) { return response(req -> { - if (licenseName.isEmpty()) { - return badRequest(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNameIsEmpty")); - } Dataset dataset = findDatasetOrDie(datasetId); - License license = licenseSvc.getByNameOrUri(licenseName); - if (license == null) { - return notFound(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNotFound", List.of(licenseName))); + if (requestBody.getName() != null && !requestBody.getName().isEmpty()) { + String licenseName = requestBody.getName(); + License license = licenseSvc.getByNameOrUri(licenseName); + if (license == null) { + return notFound(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNotFound", List.of(licenseName))); + } + execCommand(new UpdateDatasetLicenseCommand(req, dataset, license)); + return ok(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success")); + } else if (requestBody.getCustomTerms() != null) { + CustomTermsDTO customTerms = requestBody.getCustomTerms(); + execCommand(new UpdateDatasetLicenseCommand(req, dataset, customTerms.toTermsOfUseAndAccess())); + return ok(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success")); + } else { + return badRequest(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNameIsEmpty")); } - execCommand(new UpdateDatasetLicenseCommand(req, dataset, license)); - return ok(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success")); }, getRequestUser(crc)); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java b/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java new file mode 100644 index 00000000000..ef60241ebbc --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java @@ -0,0 +1,94 @@ +package edu.harvard.iq.dataverse.api.dto; + +import edu.harvard.iq.dataverse.TermsOfUseAndAccess; +import edu.harvard.iq.dataverse.license.License; + +import java.util.List; + +public class CustomTermsDTO { + private String termsOfUse; + private String confidentialityDeclaration; + private String specialPermissions; + private String restrictions; + private String citationRequirements; + private String depositorRequirements; + private String conditions; + private String disclaimer; + + public String getTermsOfUse() { + return termsOfUse; + } + + public void setTermsOfUse(String termsOfUse) { + this.termsOfUse = termsOfUse; + } + + public String getConfidentialityDeclaration() { + return confidentialityDeclaration; + } + + public void setConfidentialityDeclaration(String confidentialityDeclaration) { + this.confidentialityDeclaration = confidentialityDeclaration; + } + + public String getSpecialPermissions() { + return specialPermissions; + } + + public void setSpecialPermissions(String specialPermissions) { + this.specialPermissions = specialPermissions; + } + + public String getRestrictions() { + return restrictions; + } + + public void setRestrictions(String restrictions) { + this.restrictions = restrictions; + } + + public String getCitationRequirements() { + return citationRequirements; + } + + public void setCitationRequirements(String citationRequirements) { + this.citationRequirements = citationRequirements; + } + + public String getDepositorRequirements() { + return depositorRequirements; + } + + public void setDepositorRequirements(String depositorRequirements) { + this.depositorRequirements = depositorRequirements; + } + + public String getConditions() { + return conditions; + } + + public void setConditions(String conditions) { + this.conditions = conditions; + } + + public String getDisclaimer() { + return disclaimer; + } + + public void setDisclaimer(String disclaimer) { + this.disclaimer = disclaimer; + } + + public TermsOfUseAndAccess toTermsOfUseAndAccess() { + TermsOfUseAndAccess termsOfUseAndAccess = new TermsOfUseAndAccess(); + termsOfUseAndAccess.setTermsOfUse(termsOfUse); + termsOfUseAndAccess.setConfidentialityDeclaration(confidentialityDeclaration); + termsOfUseAndAccess.setSpecialPermissions(specialPermissions); + termsOfUseAndAccess.setRestrictions(restrictions); + termsOfUseAndAccess.setCitationRequirements(citationRequirements); + termsOfUseAndAccess.setDepositorRequirements(depositorRequirements); + termsOfUseAndAccess.setConditions(conditions); + termsOfUseAndAccess.setDisclaimer(disclaimer); + return termsOfUseAndAccess; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java b/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java new file mode 100644 index 00000000000..65a92ddaa62 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java @@ -0,0 +1,30 @@ +package edu.harvard.iq.dataverse.api.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +// This DTO acts as a wrapper for the request body. +// It can accept EITHER a 'name' or a 'customTerms' object. +// @JsonInclude is used so that null fields are not serialized in responses. +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LicenseUpdateRequest { + + private String name; + private CustomTermsDTO customTerms; + + // Getters and setters + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CustomTermsDTO getCustomTerms() { + return customTerms; + } + + public void setCustomTerms(CustomTermsDTO customTerms) { + this.customTerms = customTerms; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java index dcc08d38935..1262eeecefd 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java @@ -15,7 +15,8 @@ @RequiredPermissions(Permission.EditDataset) public class UpdateDatasetLicenseCommand extends AbstractVoidCommand { private final Dataset dataset; - private final License license; + private License license = null; + private TermsOfUseAndAccess customTermsOfUseAndAccess = null; public UpdateDatasetLicenseCommand(DataverseRequest aRequest, Dataset dataset, License license) { super(aRequest, dataset); @@ -23,19 +24,30 @@ public UpdateDatasetLicenseCommand(DataverseRequest aRequest, Dataset dataset, L this.license = license; } + public UpdateDatasetLicenseCommand(DataverseRequest aRequest, Dataset dataset, TermsOfUseAndAccess customTermsOfUseAndAccess) { + super(aRequest, dataset); + this.dataset = dataset; + this.customTermsOfUseAndAccess = customTermsOfUseAndAccess; + } + + @Override protected void executeImpl(CommandContext ctxt) throws CommandException { - if (!license.isActive()) { - throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.licenseNotActive", List.of(license.getName())), this); - } - DatasetVersion datasetVersion = dataset.getOrCreateEditVersion(); + datasetVersion.setVersionState(DatasetVersion.VersionState.DRAFT); - TermsOfUseAndAccess termsOfUseAndAccess = datasetVersion.getTermsOfUseAndAccess(); - termsOfUseAndAccess.setLicense(license); + if (license != null) { + if (!license.isActive()) { + throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.licenseNotActive", List.of(license.getName())), this); + } + TermsOfUseAndAccess termsOfUseAndAccess = datasetVersion.getTermsOfUseAndAccess(); + termsOfUseAndAccess.setLicense(license); - datasetVersion.setVersionState(DatasetVersion.VersionState.DRAFT); + ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); + } else if (customTermsOfUseAndAccess != null) { + datasetVersion.setTermsOfUseAndAccess(customTermsOfUseAndAccess); - ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); + ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); + } } } diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java index 896f24d7ac7..2a11ba75f46 100644 --- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java @@ -36,6 +36,8 @@ public class UpdateDatasetLicenseCommandTest { @Spy private TermsOfUseAndAccess termsOfUseAndAccessSpy = new TermsOfUseAndAccess(); @Mock + private TermsOfUseAndAccess customTermsOfUseAndAccessMock; + @Mock private DataverseEngine dataverseEngineMock; @Mock private CommandContext commandContextMock; @@ -85,4 +87,18 @@ public void execute_shouldThrowException_whenLicenseIsNotActive() { InvalidCommandArgumentsException exception = assertThrows(InvalidCommandArgumentsException.class, () -> sut.execute(commandContextMock)); assertEquals(expectedMessage, exception.getMessage()); } + + @Test + public void execute_shouldUpdateCustomTermsAndSetVersionStateToDraft() throws CommandException { + // Arrange + UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, customTermsOfUseAndAccessMock); + + // Act + sut.execute(commandContextMock); + + // Assert + verify(datasetVersionMock).setVersionState(DatasetVersion.VersionState.DRAFT); + verify(datasetVersionMock).setTermsOfUseAndAccess(customTermsOfUseAndAccessMock); + verify(commandContextMock).engine(); + } } From 45c3e04e4c0b3ceac82dedafca39c9becd96b357 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Sep 2025 13:56:51 +0100 Subject: [PATCH 09/16] Changed: naming and tweaks --- src/main/java/edu/harvard/iq/dataverse/api/Datasets.java | 6 +++--- .../harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 420ebcbe0b7..0a9419794b5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -6121,9 +6121,9 @@ public Response deleteVersionNote(@Context ContainerRequestContext crc, @PathPar @PUT @AuthRequired @Path("{id}/license") - public Response updateVersionLicense(@Context ContainerRequestContext crc, - @PathParam("id") String datasetId, - LicenseUpdateRequest requestBody) { + public Response updateLicense(@Context ContainerRequestContext crc, + @PathParam("id") String datasetId, + LicenseUpdateRequest requestBody) { return response(req -> { Dataset dataset = findDatasetOrDie(datasetId); if (requestBody.getName() != null && !requestBody.getName().isEmpty()) { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java b/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java index 65a92ddaa62..bba9dab7a25 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/dto/LicenseUpdateRequest.java @@ -4,14 +4,12 @@ // This DTO acts as a wrapper for the request body. // It can accept EITHER a 'name' or a 'customTerms' object. -// @JsonInclude is used so that null fields are not serialized in responses. @JsonInclude(JsonInclude.Include.NON_NULL) public class LicenseUpdateRequest { private String name; private CustomTermsDTO customTerms; - // Getters and setters public String getName() { return name; } From 8342c0ac9c19adbafe9aca688292118ca3f592ae Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 17 Sep 2025 15:52:43 +0100 Subject: [PATCH 10/16] Added: integration tests, tweaks and improved error handling --- .../iq/dataverse/api/dto/CustomTermsDTO.java | 3 - .../impl/UpdateDatasetLicenseCommand.java | 5 + src/main/java/propertyFiles/Bundle.properties | 1 + .../harvard/iq/dataverse/api/DatasetsIT.java | 114 ++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 8 ++ .../impl/UpdateDatasetLicenseCommandTest.java | 18 +++ 6 files changed, 146 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java b/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java index ef60241ebbc..b2d00e01ef5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/dto/CustomTermsDTO.java @@ -1,9 +1,6 @@ package edu.harvard.iq.dataverse.api.dto; import edu.harvard.iq.dataverse.TermsOfUseAndAccess; -import edu.harvard.iq.dataverse.license.License; - -import java.util.List; public class CustomTermsDTO { private String termsOfUse; diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java index 1262eeecefd..a4734731064 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java @@ -45,6 +45,11 @@ protected void executeImpl(CommandContext ctxt) throws CommandException { ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); } else if (customTermsOfUseAndAccess != null) { + if (customTermsOfUseAndAccess.getTermsOfUse() == null || customTermsOfUseAndAccess.getTermsOfUse().isBlank()) { + throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.customTermsOfUseNotProvided"), this); + } + + customTermsOfUseAndAccess.setDatasetVersion(datasetVersion); datasetVersion.setTermsOfUseAndAccess(customTermsOfUseAndAccess); ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 9c2cd88e6f3..8c85978f6e3 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -3242,3 +3242,4 @@ getUserPermittedCollectionsCommand.errors.permissionNotValid=Permission not vali #UpdateDatasetLicenseCommand.java updateDatasetLicenseCommand.errors.licenseNotActive=License {0} cannot be set because it is not active. +updateDatasetLicenseCommand.errors.customTermsOfUseNotProvided=Terms of use text should be provided in custom terms. diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index b273502e6ed..9506887bc78 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -6905,6 +6905,120 @@ public void testUpdateMultipleFileMetadata() { .statusCode(OK.getStatusCode()); } + @Test + public void testUpdateLicense() { + Response createUser = UtilIT.createRandomUser(); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + // Test setup: Create a user and a published dataverse to host the dataset. + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + String ownerAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + UtilIT.publishDataverseViaNativeApi(ownerAlias, apiToken); + + // Test setup: Create and publish a dataset within the new dataverse. + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(ownerAlias, apiToken); + Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse); + String datasetPersistentId = UtilIT.getDatasetPersistentIdFromResponse(createDatasetResponse); + UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken); + + // Verify the dataset's initial state, ensuring it has the default CC0 1.0 license. + Response getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST, apiToken); + getDatasetVersion.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.license.name", equalTo("CC0 1.0")); + + // Test case 1: Update to a valid, predefined license (CC BY 4.0). + Response updateLicenseResponse = UtilIT.updateLicense(datasetId.toString(), "{ \"name\": \"CC BY 4.0\" }", apiToken); + updateLicenseResponse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.message", equalTo(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success"))); + + getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST, apiToken); + getDatasetVersion.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.license.name", equalTo("CC BY 4.0")); + + // Test case 2: Attempt to update with an invalid license name and verify the expected error. + String testInvalidLicenseName = "INVALID LICENSE 4.0"; + updateLicenseResponse = UtilIT.updateLicense(datasetId.toString(), "{ \"name\": \"" + testInvalidLicenseName + "\" }", apiToken); + updateLicenseResponse.then().assertThat() + .statusCode(NOT_FOUND.getStatusCode()) + .body("message", equalTo(BundleUtil.getStringFromBundle("datasets.api.updateLicense.licenseNotFound", List.of(testInvalidLicenseName)))); + + // Test case 3: Update with custom terms, providing only the required 'termsOfUse' field. + String jsonString = """ + { + "customTerms": { + "termsOfUse": "testTermsOfUse" + } + } + """; + + updateLicenseResponse = UtilIT.updateLicense(datasetId.toString(), jsonString, apiToken); + updateLicenseResponse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.message", equalTo(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success"))); + + getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST, apiToken); + getDatasetVersion.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.license", equalTo(null)) + .body("data.termsOfUse", equalTo("testTermsOfUse")) + .body("data.confidentialityDeclaration", equalTo(null)) + .body("data.specialPermissions", equalTo(null)) + .body("data.restrictions", equalTo(null)) + .body("data.citationRequirements", equalTo(null)) + .body("data.depositorRequirements", equalTo(null)) + .body("data.conditions", equalTo(null)) + .body("data.disclaimer", equalTo(null)); + + // Test case 4: Update with a complete set of custom terms, including all optional fields. + jsonString = """ + { + "customTerms": { + "termsOfUse": "testTermsOfUse", + "confidentialityDeclaration": "testConfidentialityDeclaration", + "specialPermissions": "testSpecialPermissions", + "restrictions": "testRestrictions", + "citationRequirements": "testCitationRequirements", + "depositorRequirements": "testDepositorRequirements", + "conditions": "testConditions", + "disclaimer": "testDisclaimer" + } + } + """; + + updateLicenseResponse = UtilIT.updateLicense(datasetId.toString(), jsonString, apiToken); + updateLicenseResponse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.message", equalTo(BundleUtil.getStringFromBundle("datasets.api.updateLicense.success"))); + + getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST, apiToken); + getDatasetVersion.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.license", equalTo(null)) + .body("data.termsOfUse", equalTo("testTermsOfUse")) + .body("data.confidentialityDeclaration", equalTo("testConfidentialityDeclaration")) + .body("data.specialPermissions", equalTo("testSpecialPermissions")) + .body("data.restrictions", equalTo("testRestrictions")) + .body("data.citationRequirements", equalTo("testCitationRequirements")) + .body("data.depositorRequirements", equalTo("testDepositorRequirements")) + .body("data.conditions", equalTo("testConditions")) + .body("data.disclaimer", equalTo("testDisclaimer")); + + // Test case 5: Ensure that providing an empty 'customTerms' object results in a validation error. + jsonString = """ + { + "customTerms": {} + } + """; + + updateLicenseResponse = UtilIT.updateLicense(datasetId.toString(), jsonString, apiToken); + updateLicenseResponse.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("message", equalTo(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.customTermsOfUseNotProvided"))); + } + private String getSuperuserToken() { Response createResponse = UtilIT.createRandomUser(); String adminApiToken = UtilIT.getApiTokenFromResponse(createResponse); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index eba8181e566..01711ab1ad4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4981,4 +4981,12 @@ public static Response callCallbackUrl(String callbackUrl) { .when() .get(callbackUrl); } + + public static Response updateLicense(String datasetId, String licenseOrCustomTerms, String apiToken) { + return given() + .body(licenseOrCustomTerms) + .contentType(ContentType.JSON) + .header(API_TOKEN_HTTP_HEADER, apiToken) + .put("/api/datasets/" + datasetId + "/license"); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java index 2a11ba75f46..c6da8c0ee65 100644 --- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java @@ -12,6 +12,9 @@ import edu.harvard.iq.dataverse.util.BundleUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -91,6 +94,7 @@ public void execute_shouldThrowException_whenLicenseIsNotActive() { @Test public void execute_shouldUpdateCustomTermsAndSetVersionStateToDraft() throws CommandException { // Arrange + when(customTermsOfUseAndAccessMock.getTermsOfUse()).thenReturn("custom terms"); UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, customTermsOfUseAndAccessMock); // Act @@ -101,4 +105,18 @@ public void execute_shouldUpdateCustomTermsAndSetVersionStateToDraft() throws Co verify(datasetVersionMock).setTermsOfUseAndAccess(customTermsOfUseAndAccessMock); verify(commandContextMock).engine(); } + + @ParameterizedTest + @NullSource + @ValueSource(strings = {"", " "}) + public void execute_shouldThrowException_whenCustomTermsOfUseAreNullOrBlank(String invalidTerms) { + // Arrange + when(customTermsOfUseAndAccessMock.getTermsOfUse()).thenReturn(invalidTerms); + UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, customTermsOfUseAndAccessMock); + String expectedMessage = BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.customTermsOfUseNotProvided"); + + // Act & Assert + InvalidCommandArgumentsException exception = assertThrows(InvalidCommandArgumentsException.class, () -> sut.execute(commandContextMock)); + assertEquals(expectedMessage, exception.getMessage()); + } } From 86510d84ed8df4d0c134782d894e5e5d5283ea6f Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 17 Sep 2025 18:50:17 +0100 Subject: [PATCH 11/16] Added: DatasetsIT test case for updating license with insufficient permissions --- .../java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 9506887bc78..0d52528adde 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -7017,6 +7017,14 @@ public void testUpdateLicense() { updateLicenseResponse.then().assertThat() .statusCode(BAD_REQUEST.getStatusCode()) .body("message", equalTo(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.customTermsOfUseNotProvided"))); + + // Test case 6: The operation should return a permissions error when invoked by a user with insufficient permissions. + createUser = UtilIT.createRandomUser(); + apiToken = UtilIT.getApiTokenFromResponse(createUser); + + updateLicenseResponse = UtilIT.updateLicense(datasetId.toString(), "{ \"name\": \"CC BY 4.0\" }", apiToken); + updateLicenseResponse.then().assertThat() + .statusCode(UNAUTHORIZED.getStatusCode()); } private String getSuperuserToken() { From 806bbb322ff6b858d07425317362c189d79d25c4 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 17 Sep 2025 19:07:14 +0100 Subject: [PATCH 12/16] Added: docs for new datasets updateLicense endpoint --- doc/sphinx-guides/source/api/native-api.rst | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index fa4b4611559..4dc08a73aca 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -4058,6 +4058,48 @@ Upon success, the API will return a JSON response with a success message and the The API call will report a 400 (BAD REQUEST) error if any of the files specified do not exist or are not in the latest version of the specified dataset. The ``fileIds`` in the JSON payload should be an array of file IDs that you want to delete from the dataset. +Update Dataset License +~~~~~~~~~~~~~~~~~~~~~~ + +Updates the license of a dataset by applying it to the draft version, or by creating a draft if none exists. + +The JSON representation of a license can take two forms, depending on whether you want to specify a predefined license or define custom terms of use and access. + +To set a predefined license (e.g., CC BY 4.0), provide a JSON body with the license name: + + { + "name": "CC BY 4.0" + } + +To define custom terms of use and access, provide a JSON body with the following properties. All fields within ``customTerms`` are optional, except for the ``termsOfUse`` field, which is required: + + { + "customTerms": { + "termsOfUse": "Your terms of use", + "confidentialityDeclaration": "Your confidentiality declaration", + "specialPermissions": "Your special permissions", + "restrictions": "Your restrictions", + "citationRequirements": "Your citation requirements", + "depositorRequirements": "Your depositor requirements", + "conditions": "Your conditions", + "disclaimer": "Your disclaimer" + } + } + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export ID=3 + export FILE_PATH=license.json + + curl -H "X-Dataverse-key:$API_TOKEN" -X PUT "$SERVER_URL/api/datasets/$ID/license" -H "Content-type:application/json" --upload-file $FILE_PATH + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X PUT "https://demo.dataverse.org/api/datasets/3/license" -H "Content-type:application/json" --upload-file license.json Files ----- From c857a159f8e84dde569d6a8eaafa493589511ccd Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 17 Sep 2025 19:10:12 +0100 Subject: [PATCH 13/16] Added: release notes for #11771 --- .../11771-update-dataset-license-api.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 doc/release-notes/11771-update-dataset-license-api.md diff --git a/doc/release-notes/11771-update-dataset-license-api.md b/doc/release-notes/11771-update-dataset-license-api.md new file mode 100644 index 00000000000..e87f7e0f93f --- /dev/null +++ b/doc/release-notes/11771-update-dataset-license-api.md @@ -0,0 +1,13 @@ +## New Endpoint: `/datasets/{id}/license` + +A new endpoint has been implemented to manage dataset licenses. + +### Functionality +- Updates the license of a dataset by applying it to the draft version. +- If no draft exists, a new one is automatically created. + +### Usage +This endpoint supports two ways of defining a license: +1. **Predefined License** – Provide the license name (e.g., `CC BY 4.0`). +2. **Custom Terms of Use and Access** – Provide a JSON body with the `customTerms` object. + - All fields are optional **except** `termsOfUse`, which is required. From 49945f772f3ed7016d8bb2b88b56f4cb2ca30a67 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 24 Oct 2025 18:33:50 +0100 Subject: [PATCH 14/16] Fixed: missing import statement --- .../java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java b/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java index 5106035366c..07e87a9b86a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java @@ -15,10 +15,7 @@ import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; +import java.net.*; import java.util.Iterator; import java.util.List; import java.util.concurrent.Future; From e2ef4a4114bffc6e899d2c2a9ffe6060f83ee0cb Mon Sep 17 00:00:00 2001 From: GPortas Date: Sat, 25 Oct 2025 16:57:02 +0100 Subject: [PATCH 15/16] Fixed: UpdateDatasetLicenseCommand by not overwriting dataset TermsOfUseAndAccess but merging new fields into the existing one --- .../impl/UpdateDatasetLicenseCommand.java | 26 ++++++++++++--- .../impl/UpdateDatasetLicenseCommandTest.java | 33 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java index a4734731064..37f97bf0db1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommand.java @@ -48,11 +48,29 @@ protected void executeImpl(CommandContext ctxt) throws CommandException { if (customTermsOfUseAndAccess.getTermsOfUse() == null || customTermsOfUseAndAccess.getTermsOfUse().isBlank()) { throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("updateDatasetLicenseCommand.errors.customTermsOfUseNotProvided"), this); } - - customTermsOfUseAndAccess.setDatasetVersion(datasetVersion); - datasetVersion.setTermsOfUseAndAccess(customTermsOfUseAndAccess); - + TermsOfUseAndAccess termsToUpdate = datasetVersion.getTermsOfUseAndAccess(); + applyCustomTerms(termsToUpdate, customTermsOfUseAndAccess); + termsToUpdate.setLicense(null); + datasetVersion.setTermsOfUseAndAccess(termsToUpdate); ctxt.engine().submit(new UpdateDatasetVersionCommand(this.dataset, getRequest())); } } + + /** + * Copies all custom term-related fields from the 'source' object + * to the 'target' object. + * + * @param target The TermsOfUseAndAccess object to be modified + * @param source The TermsOfUseAndAccess object containing the new data + */ + private void applyCustomTerms(TermsOfUseAndAccess target, TermsOfUseAndAccess source) { + target.setTermsOfUse(source.getTermsOfUse()); + target.setConfidentialityDeclaration(source.getConfidentialityDeclaration()); + target.setSpecialPermissions(source.getSpecialPermissions()); + target.setRestrictions(source.getRestrictions()); + target.setCitationRequirements(source.getCitationRequirements()); + target.setDepositorRequirements(source.getDepositorRequirements()); + target.setConditions(source.getConditions()); + target.setDisclaimer(source.getDisclaimer()); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java index c6da8c0ee65..78d5e99546c 100644 --- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetLicenseCommandTest.java @@ -94,7 +94,25 @@ public void execute_shouldThrowException_whenLicenseIsNotActive() { @Test public void execute_shouldUpdateCustomTermsAndSetVersionStateToDraft() throws CommandException { // Arrange - when(customTermsOfUseAndAccessMock.getTermsOfUse()).thenReturn("custom terms"); + String termsOfUse = "custom terms"; + String confidentialityDeclaration = "confidentiality"; + String specialPermissions = "special permissions"; + String restrictions = "restrictions"; + String citationRequirements = "citation"; + String depositorRequirements = "depositor"; + String conditions = "conditions"; + String disclaimer = "disclaimer"; + + when(customTermsOfUseAndAccessMock.getTermsOfUse()).thenReturn(termsOfUse); + when(customTermsOfUseAndAccessMock.getConfidentialityDeclaration()).thenReturn(confidentialityDeclaration); + when(customTermsOfUseAndAccessMock.getSpecialPermissions()).thenReturn(specialPermissions); + when(customTermsOfUseAndAccessMock.getRestrictions()).thenReturn(restrictions); + when(customTermsOfUseAndAccessMock.getCitationRequirements()).thenReturn(citationRequirements); + when(customTermsOfUseAndAccessMock.getDepositorRequirements()).thenReturn(depositorRequirements); + when(customTermsOfUseAndAccessMock.getConditions()).thenReturn(conditions); + when(customTermsOfUseAndAccessMock.getDisclaimer()).thenReturn(disclaimer); + + termsOfUseAndAccessSpy.setLicense(activeLicense); UpdateDatasetLicenseCommand sut = new UpdateDatasetLicenseCommand(dataverseRequestStub, datasetMock, customTermsOfUseAndAccessMock); // Act @@ -102,7 +120,18 @@ public void execute_shouldUpdateCustomTermsAndSetVersionStateToDraft() throws Co // Assert verify(datasetVersionMock).setVersionState(DatasetVersion.VersionState.DRAFT); - verify(datasetVersionMock).setTermsOfUseAndAccess(customTermsOfUseAndAccessMock); + + assertEquals(termsOfUse, termsOfUseAndAccessSpy.getTermsOfUse()); + assertEquals(confidentialityDeclaration, termsOfUseAndAccessSpy.getConfidentialityDeclaration()); + assertEquals(specialPermissions, termsOfUseAndAccessSpy.getSpecialPermissions()); + assertEquals(restrictions, termsOfUseAndAccessSpy.getRestrictions()); + assertEquals(citationRequirements, termsOfUseAndAccessSpy.getCitationRequirements()); + assertEquals(depositorRequirements, termsOfUseAndAccessSpy.getDepositorRequirements()); + assertEquals(conditions, termsOfUseAndAccessSpy.getConditions()); + assertEquals(disclaimer, termsOfUseAndAccessSpy.getDisclaimer()); + assertEquals(null, termsOfUseAndAccessSpy.getLicense()); + + verify(datasetVersionMock).setTermsOfUseAndAccess(termsOfUseAndAccessSpy); verify(commandContextMock).engine(); } From 676f834cbfbeefc1a58c882ebad016012fb21aa8 Mon Sep 17 00:00:00 2001 From: GPortas Date: Sat, 25 Oct 2025 17:05:21 +0100 Subject: [PATCH 16/16] Fixed: native api rst format --- doc/sphinx-guides/source/api/native-api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 04eab080bae..ddfc73b3c4f 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -4366,12 +4366,16 @@ The JSON representation of a license can take two forms, depending on whether yo To set a predefined license (e.g., CC BY 4.0), provide a JSON body with the license name: +.. code-block:: json + { "name": "CC BY 4.0" } To define custom terms of use and access, provide a JSON body with the following properties. All fields within ``customTerms`` are optional, except for the ``termsOfUse`` field, which is required: +.. code-block:: json + { "customTerms": { "termsOfUse": "Your terms of use",