From d4e8988daa2f5c071864c32b7144e492644c7f73 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Thu, 29 Jan 2026 11:56:54 +0100 Subject: [PATCH 1/2] test: add test for duplicate role assignment --- .../iq/dataverse/api/DataversesIT.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index dc658c7134b..d5e1ea74275 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -2867,4 +2867,46 @@ public void testDataverseMetadataLanguage() { singleLang.then().assertThat().body("data", equalTo(List.of(Map.of("locale", "en", "title", "English")))); } + @Test + public void testAssignRoleOnDataverse() { + // Create user + Response createUser = UtilIT.createRandomUser(); + createUser.prettyPrint(); + String username = UtilIT.getUsernameFromResponse(createUser); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + // Create collection as that user + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.prettyPrint(); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + // Create second user + Response createSecondUserResponse = UtilIT.createRandomUser(); + String secondUsername = UtilIT.getUsernameFromResponse(createSecondUserResponse); + String secondApiToken = UtilIT.getApiTokenFromResponse(createSecondUserResponse); + + // Let the first user assign a role on their collection to the second user + Response grantRoleResponse = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR, "@" + secondUsername, apiToken); + grantRoleResponse.prettyPrint(); + grantRoleResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Let the first user assign the same role to the same user again + Response grantRoleTwiceResponse = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR, "@" + secondUsername, apiToken); + grantRoleTwiceResponse.prettyPrint(); + grantRoleTwiceResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Clean up + Response deleteDataverse = UtilIT.deleteDataverse(dataverseAlias, apiToken); + deleteDataverse.prettyPrint(); + deleteDataverse.then().assertThat().statusCode(OK.getStatusCode()); + + Response deleteUserResponse = UtilIT.deleteUser(username); + deleteUserResponse.prettyPrint(); + assertEquals(200, deleteUserResponse.getStatusCode()); + + Response deleteSecondUserResponse = UtilIT.deleteUser(secondUsername); + deleteSecondUserResponse.prettyPrint(); + assertEquals(200, deleteSecondUserResponse.getStatusCode()); + } + } From 832436ad233657935827d62aa97965de2dc4f59a Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Mon, 2 Feb 2026 17:39:21 +0100 Subject: [PATCH 2/2] feat: return 409 when creating duplicate role assignment --- .../harvard/iq/dataverse/api/AbstractApiBean.java | 2 ++ .../command/exception/ConflictException.java | 14 ++++++++++++++ .../engine/command/impl/AssignRoleCommand.java | 3 ++- .../edu/harvard/iq/dataverse/api/DataversesIT.java | 4 ++-- 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/main/java/edu/harvard/iq/dataverse/engine/command/exception/ConflictException.java 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 2ee5730153e..1c8984f47d3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -892,6 +892,8 @@ protected T execCommand( Command cmd ) throws WrappedResponse { throw new WrappedResponse(ex, badRequest(ex.getMessage(), ex.getFieldErrors())); } catch (InvalidCommandArgumentsException ex) { throw new WrappedResponse(ex, error(Status.BAD_REQUEST, ex.getMessage())); + } catch (ConflictException ex) { + throw new WrappedResponse(ex, conflict(ex.getMessage())); } catch (CommandException ex) { Logger.getLogger(AbstractApiBean.class.getName()).log(Level.SEVERE, "Error while executing command " + cmd, ex); throw new WrappedResponse(ex, error(Status.INTERNAL_SERVER_ERROR, ex.getMessage())); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/exception/ConflictException.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/exception/ConflictException.java new file mode 100644 index 00000000000..6927bae630d --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/exception/ConflictException.java @@ -0,0 +1,14 @@ +package edu.harvard.iq.dataverse.engine.command.exception; + +import edu.harvard.iq.dataverse.engine.command.Command; + +/** + * An exception raised when a command cannot be executed because it clashes with the current state, e.g. when trying to + * create a resource that already exists. + */ +public class ConflictException extends CommandException { + + public ConflictException(String message, Command aCommand) { + super(message, aCommand); + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AssignRoleCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AssignRoleCommand.java index db3c41f0f03..4b4c42e15d9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AssignRoleCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AssignRoleCommand.java @@ -16,6 +16,7 @@ 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.ConflictException; import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException; import edu.harvard.iq.dataverse.util.BundleUtil; @@ -73,7 +74,7 @@ public RoleAssignment execute(CommandContext ctxt) throws CommandException { } } if(isExistingRole(ctxt)){ - throw new IllegalCommandException(BundleUtil.getStringFromBundle("datasets.api.grant.role.assignee.has.role.error"), this); + throw new ConflictException(BundleUtil.getStringFromBundle("datasets.api.grant.role.assignee.has.role.error"), this); } // TODO make sure the role is defined on the dataverse. RoleAssignment roleAssignment = new RoleAssignment(role, grantee, defPoint, privateUrlToken, anonymizedAccess); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index d5e1ea74275..632ac39eb4e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -2890,10 +2890,10 @@ public void testAssignRoleOnDataverse() { grantRoleResponse.prettyPrint(); grantRoleResponse.then().assertThat().statusCode(OK.getStatusCode()); - // Let the first user assign the same role to the same user again + // Let the first user assign the same role to the same user again -> 409 Conflict Response grantRoleTwiceResponse = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR, "@" + secondUsername, apiToken); grantRoleTwiceResponse.prettyPrint(); - grantRoleTwiceResponse.then().assertThat().statusCode(OK.getStatusCode()); + grantRoleTwiceResponse.then().assertThat().statusCode(CONFLICT.getStatusCode()); // Clean up Response deleteDataverse = UtilIT.deleteDataverse(dataverseAlias, apiToken);