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; } }