diff --git a/Jenkinsfile b/Jenkinsfile
index 21a05f414b..e839638351 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -11,7 +11,7 @@ node() { // No specific label
disableConcurrentBuilds()
])
- String defaultEmailRecipients = env.EMAIL_RECIPIENTS
+ String defaultEmailRecipients = env.EMAIL_SCM_RECIPIENTS
catchError {
@@ -40,5 +40,8 @@ node() { // No specific label
// Archive Unit and integration test results, if any
junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml'
- mailIfStatusChanged(findEmailRecipients(defaultEmailRecipients))
+ // Find maven warnings and visualize in job
+ warnings consoleParsers: [[parserName: 'Maven']], canRunOnFailed: true
+
+ mailIfStatusChanged(defaultEmailRecipients)
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionDto.java
index 8a030e5070..c10e18267c 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionDto.java
@@ -3,10 +3,10 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
-@Data @EqualsAndHashCode(callSuper = false)
+@Getter @Setter
class CollectionDto extends HalRepresentation {
private int page;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java
index 056d8a7857..12a3cd58f1 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java
@@ -2,6 +2,7 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
+import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.group.Group;
@@ -25,9 +26,9 @@ import java.io.IOException;
import static sonia.scm.api.v2.resources.ResourceLinks.group;
-@Produces(VndMediaType.GROUP_COLLECTION)
public class GroupCollectionResource {
- public static final int DEFAULT_PAGE_SIZE = 10;
+
+ private static final int DEFAULT_PAGE_SIZE = 10;
private final GroupDtoToGroupMapper dtoToGroupMapper;
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
@@ -42,21 +43,23 @@ public class GroupCollectionResource {
/**
* Returns all groups for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
- * Note: This method requires admin privileges.
+ *
+ * Note: This method requires "group" privilege.
*
* @param request the current request
* @param page the number of the requested page
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
* @param sortBy sort parameter
* @param desc sort direction desc or aesc
- * @return
*/
@GET
@Path("")
+ @Produces(VndMediaType.GROUP_COLLECTION)
@TypeHint(GroupDto[].class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getAll(@Context Request request,
@@ -65,7 +68,8 @@ public class GroupCollectionResource {
@QueryParam("sortby") String sortBy,
@DefaultValue("false")
@QueryParam("desc") boolean desc) {
- return adapter.getAll(page, pageSize, sortBy, desc, pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
+ return adapter.getAll(page, pageSize, sortBy, desc,
+ pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
}
/**
@@ -75,17 +79,19 @@ public class GroupCollectionResource {
*/
@POST
@Path("")
+ @Consumes(VndMediaType.GROUP)
@StatusCodes({
- @ResponseCode(code = 201, condition = "create success", additionalHeaders = {
- @ResponseHeader(name = "Location", description = "uri to the created group")
- }),
- @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"),
+ @ResponseCode(code = 201, condition = "create success"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
@ResponseCode(code = 409, condition = "conflict, a group with this name already exists"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
- @Consumes(VndMediaType.GROUP)
+ @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group"))
public Response create(@Context UriInfo uriInfo, GroupDto groupDto) throws IOException, GroupException {
- return adapter.create(groupDto, () -> dtoToGroupMapper.map(groupDto), group -> group(uriInfo).self(group.getName()));
+ return adapter.create(groupDto,
+ () -> dtoToGroupMapper.map(groupDto),
+ group -> group(uriInfo).self(group.getName()));
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java
index 65dae5f5f1..081ae817ae 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java
@@ -3,16 +3,16 @@ package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.Setter;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-@Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true)
+@Getter @Setter @NoArgsConstructor
public class GroupDto extends HalRepresentation {
private Instant creationDate;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java
index 6f198d642d..dfd3b3b1e4 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java
@@ -9,13 +9,18 @@ import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
-import javax.ws.rs.*;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-@Produces(VndMediaType.GROUP)
public class GroupResource {
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
@@ -30,12 +35,23 @@ public class GroupResource {
this.adapter = new ResourceManagerAdapter<>(manager);
}
- @Path("")
+ /**
+ * Returns a group.
+ *
+ * Note: This method requires "group" privilege.
+ *
+ * @param request the current request
+ * @param id the id/name of the group
+ *
+ */
@GET
+ @Path("")
+ @Produces(VndMediaType.GROUP)
@TypeHint(GroupDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user has no privileges to read the group"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the group"),
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@@ -43,26 +59,43 @@ public class GroupResource {
return adapter.get(id, groupToGroupDtoMapper::map);
}
- @Path("")
+ /**
+ * Deletes a group.
+ *
+ * Note: This method requires "group" privilege.
+ *
+ * @param name the name of the group to delete.
+ *
+ */
@DELETE
- public Response delete(@PathParam("id") String id) {
- throw new RuntimeException();
+ @Path("")
+ @StatusCodes({
+ @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ @TypeHint(TypeHint.NO_CONTENT.class)
+ public Response delete(@PathParam("id") String name) {
+ throw new UnsupportedOperationException("Not yet implemented");
}
-
/**
* Modifies the given group.
*
- * Note: This method requires "group" privileges.
+ * Note: This method requires "group" privilege.
*
* @param name name of the group to be modified
* @param groupDto group object to modify
*/
@PUT
@Path("")
+ @Consumes(VndMediaType.GROUP)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"group\" privilege"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
+ @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java
index f18df472a2..0d98d6239c 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java
@@ -4,10 +4,13 @@ import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
+/**
+ * RESTful Web Service Resource to manage groups and their members.
+ */
@Path(GroupRootResource.GROUPS_PATH_V2)
public class GroupRootResource {
- public static final String GROUPS_PATH_V2 = "v2/groups/";
+ static final String GROUPS_PATH_V2 = "v2/groups/";
private final Provider groupCollectionResource;
private final Provider groupResource;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MemberDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MemberDto.java
index fcd7b8e8ad..dd4979bb4b 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MemberDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MemberDto.java
@@ -3,12 +3,11 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.Setter;
-@NoArgsConstructor @AllArgsConstructor
-@Data @EqualsAndHashCode(callSuper = false)
+@NoArgsConstructor @AllArgsConstructor @Getter @Setter
public class MemberDto extends HalRepresentation {
private String name;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceManagerAdapter.java
index 2bc2193561..c2aab1a058 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceManagerAdapter.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceManagerAdapter.java
@@ -14,13 +14,21 @@ import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+
/**
* Adapter from resource http endpoints to managers.
+ *
+ * Provides common CRUD operations and DTO to Model Object mapping to keep Resources more DRY.
+ *
* @param The type of the model object, eg. {@link sonia.scm.user.User}.
* @param The corresponding transport object, eg. {@link UserDto}.
* @param The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*/
-class ResourceManagerAdapter extends AbstractManagerResource {
+@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
+class ResourceManagerAdapter extends AbstractManagerResource {
ResourceManagerAdapter(Manager manager) {
super(manager);
@@ -49,6 +57,9 @@ class ResourceManagerAdapter modelObjectSupplier, Function uriCreator) throws IOException, EXCEPTION {
if (dto == null) {
- return Response.status(400).build();
+ return Response.status(BAD_REQUEST).build();
}
MODEL_OBJECT modelObject = modelObjectSupplier.get();
manager.create(modelObject);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java
index 980ed9ce06..da1cd6e2f1 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java
@@ -11,6 +11,7 @@ import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
+import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -25,7 +26,6 @@ import java.io.IOException;
import static sonia.scm.api.v2.resources.ResourceLinks.user;
-@Produces(VndMediaType.USER_COLLECTION)
public class UserCollectionResource {
private static final int DEFAULT_PAGE_SIZE = 10;
@@ -45,7 +45,7 @@ public class UserCollectionResource {
/**
* Returns all users for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
*
- * Note: This method requires "user" privileges.
+ * Note: This method requires "user" privilege.
*
* @param request the current request
* @param page the number of the requested page
@@ -55,10 +55,12 @@ public class UserCollectionResource {
*/
@GET
@Path("")
+ @Produces(VndMediaType.USER_COLLECTION)
@TypeHint(UserDto[].class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getAll(@Context Request request,
@@ -66,30 +68,33 @@ public class UserCollectionResource {
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
@QueryParam("sortby") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
- return adapter.getAll(page, pageSize, sortBy, desc, pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
+ return adapter.getAll(page, pageSize, sortBy, desc,
+ pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
}
/**
* Creates a new user.
*
- * Note: This method requires "user" privileges.
+ * Note: This method requires "user" privilege.
*
* @param userDto The user to be created.
* @return A response with the link to the new user (if created successfully).
*/
@POST
@Path("")
+ @Consumes(VndMediaType.USER)
@StatusCodes({
- @ResponseCode(code = 201, condition = "create success", additionalHeaders = {
- @ResponseHeader(name = "Location", description = "uri to the created user")
- }),
- @ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
+ @ResponseCode(code = 201, condition = "create success"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 409, condition = "conflict, a user with this name already exists"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
public Response create(@Context UriInfo uriInfo, UserDto userDto) throws IOException, UserException {
- return adapter.create(userDto, () -> dtoToUserMapper.map(userDto, ""), user -> user(uriInfo).self(user.getName()));
+ return adapter.create(userDto,
+ () -> dtoToUserMapper.map(userDto, ""),
+ user -> user(uriInfo).self(user.getName()));
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java
index 3a5b1271fd..d87f4a74f1 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java
@@ -3,17 +3,15 @@ package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.Setter;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
-@AllArgsConstructor @NoArgsConstructor
-@Data @EqualsAndHashCode(callSuper = false)
+@NoArgsConstructor @Getter @Setter
public class UserDto extends HalRepresentation {
private boolean active;
private boolean admin;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java
index 07008e8e4c..f1d9596b3e 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java
@@ -9,6 +9,7 @@ import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
+import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
@@ -20,7 +21,6 @@ import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-@Produces(VndMediaType.USER)
public class UserResource {
private final UserDtoToUserMapper dtoToUserMapper;
@@ -38,7 +38,7 @@ public class UserResource {
/**
* Returns a user.
*
- * Note: This method requires "user" privileges.
+ * Note: This method requires "user" privilege.
*
* @param request the current request
* @param id the id/name of the user
@@ -46,10 +46,12 @@ public class UserResource {
*/
@GET
@Path("")
+ @Produces(VndMediaType.USER)
@TypeHint(UserDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user has no privileges to read the user"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@@ -57,31 +59,10 @@ public class UserResource {
return adapter.get(id, userToDtoMapper::map);
}
- /**
- * Modifies the given user.
- *
- * Note: This method requires "user" privileges.
- *
- * @param name name of the user to be modified
- * @param userDto user object to modify
- */
- @PUT
- @Path("")
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, UserDto userDto) {
- return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
- }
-
/**
* Deletes a user.
*
- * Note: This method requires "user" privileges.
+ * Note: This method requires "user" privilege.
*
* @param name the name of the user to delete.
*
@@ -89,12 +70,36 @@ public class UserResource {
@DELETE
@Path("")
@StatusCodes({
- @ResponseCode(code = 204, condition = "delete success"),
- @ResponseCode(code = 403, condition = "forbidden, the current user does not have the \"user\" privilege"),
+ @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response delete(@PathParam("id") String name) {
return adapter.delete(name);
}
+
+ /**
+ * Modifies the given user.
+ *
+ * Note: This method requires "user" privilege.
+ *
+ * @param name name of the user to be modified
+ * @param userDto user object to modify
+ */
+ @PUT
+ @Path("")
+ @Consumes(VndMediaType.USER)
+ @StatusCodes({
+ @ResponseCode(code = 204, condition = "update success"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
+ @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ @TypeHint(TypeHint.NO_CONTENT.class)
+ public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, UserDto userDto) {
+ return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
+ }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java
index 779f6323e1..9652a97404 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java
@@ -11,6 +11,7 @@ import javax.ws.rs.Path;
public class UserRootResource {
static final String USERS_PATH_V2 = "v2/users/";
+
private final Provider userCollectionResource;
private final Provider userResource;
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java
index 9e732e9212..e0b79c0ab0 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java
@@ -34,6 +34,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -69,9 +70,8 @@ public class UserRootResourceTest {
@Before
public void prepareEnvironment() throws IOException, UserException {
initMocks(this);
- User dummyUser = createDummyUser();
+ User dummyUser = createDummyUser("Neo");
when(userManager.getPage(any(), eq(0), eq(10))).thenReturn(new PageResult<>(singletonList(dummyUser), 1));
- when(userManager.get("Neo")).thenReturn(dummyUser);
doNothing().when(userManager).create(userCaptor.capture());
doNothing().when(userManager).modify(userCaptor.capture());
doNothing().when(userManager).delete(userCaptor.capture());
@@ -192,11 +192,48 @@ public class UserRootResourceTest {
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
}
- private User createDummyUser() {
+ @Test
+ public void shouldFailUpdateForDifferentIds() throws IOException, URISyntaxException, UserException {
+ URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json");
+ byte[] userJson = Resources.toByteArray(url);
+ createDummyUser("Other");
+
+ MockHttpRequest request = MockHttpRequest
+ .put("/" + UserRootResource.USERS_PATH_V2 + "Other")
+ .contentType(VndMediaType.USER)
+ .content(userJson);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
+ verify(userManager, never()).modify(any(User.class));
+ }
+
+ @Test
+ public void shouldFailUpdateForUnknownEntity() throws IOException, URISyntaxException, UserException {
+ URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json");
+ byte[] userJson = Resources.toByteArray(url);
+ when(userManager.get("Neo")).thenReturn(null);
+
+ MockHttpRequest request = MockHttpRequest
+ .put("/" + UserRootResource.USERS_PATH_V2 + "Neo")
+ .contentType(VndMediaType.USER)
+ .content(userJson);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus());
+ verify(userManager, never()).modify(any(User.class));
+ }
+
+ private User createDummyUser(String name) {
User user = new User();
- user.setName("Neo");
+ user.setName(name);
user.setPassword("redpill");
user.setCreationDate(System.currentTimeMillis());
+ when(userManager.get(name)).thenReturn(user);
return user;
}
}