From e41083628d649d20a17693c9d76dc8f52765d29d Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 27 Jun 2018 11:40:29 +0200 Subject: [PATCH 1/8] Jenkins: Send mail to standard recipients on failed builds. findEmailRecipients() from ces-build-lib works only for Git, not for hg Repos. --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 21a05f414b..fdd690a90b 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,5 @@ 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)) + mailIfStatusChanged(defaultEmailRecipients) } From 21a6b0050a66331e75718c1013ee50421783428d Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 27 Jun 2018 11:41:25 +0200 Subject: [PATCH 2/8] Jenkins: Find maven warnings and visualize in job --- Jenkinsfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index fdd690a90b..e839638351 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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' + // Find maven warnings and visualize in job + warnings consoleParsers: [[parserName: 'Maven']], canRunOnFailed: true + mailIfStatusChanged(defaultEmailRecipients) } From 267c7f231802448c976c435157545f9981728c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Jun 2018 13:30:57 +0200 Subject: [PATCH 3/8] Verify id of objects at update --- .../v2/resources/ResourceManagerAdapter.java | 5 +++ .../v2/resources/UserRootResourceTest.java | 43 +++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) 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..60ff241fbd 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,6 +14,8 @@ 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. * @param The type of the model object, eg. {@link sonia.scm.user.User}. @@ -49,6 +51,9 @@ class ResourceManagerAdapter(singletonList(dummyUser), 1)); when(userManager.get("Neo")).thenReturn(dummyUser); doNothing().when(userManager).create(userCaptor.capture()); @@ -192,9 +193,45 @@ 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); + when(userManager.get("Other")).thenReturn(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()); return user; From 9def0697fe5476d2e59db115760124089d9df5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Jun 2018 14:16:36 +0200 Subject: [PATCH 4/8] Use constant --- .../java/sonia/scm/api/v2/resources/ResourceManagerAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 60ff241fbd..98103963f9 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 @@ -72,7 +72,7 @@ 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); From cba5bf7ba4dcff6edabe2a0f07e5f7aaad50c39b Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 27 Jun 2018 14:30:24 +0200 Subject: [PATCH 5/8] Assimilates User and Group REST Resource Annotations and docs --- .../v2/resources/GroupCollectionResource.java | 28 +++++---- .../scm/api/v2/resources/GroupResource.java | 61 ++++++++++++++++--- .../api/v2/resources/GroupRootResource.java | 5 +- .../v2/resources/ResourceManagerAdapter.java | 8 ++- .../v2/resources/UserCollectionResource.java | 23 ++++--- .../scm/api/v2/resources/UserResource.java | 57 +++++++++-------- .../api/v2/resources/UserRootResource.java | 1 + 7 files changed, 126 insertions(+), 57 deletions(-) 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..c0c8079b57 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; @@ -27,7 +28,8 @@ 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 +44,22 @@ 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("") @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/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java index 4872b2ab27..3dc41a1a59 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,6 +9,7 @@ import sonia.scm.group.GroupManager; 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.GROUP) public class GroupResource { private final GroupToGroupDtoMapper groupToGroupDtoMapper; @@ -32,12 +32,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") }) @@ -45,15 +56,47 @@ 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"); } - @Path("") + /** + * Modifies the given group. + * + * Note: This method requires "group" privilege. + * + * @param name name of the group to be modified + * @param groupDto group object to modify + */ @PUT - public Response update(@PathParam("id") String id) { - throw new RuntimeException(); + @Path("") + @Consumes(VndMediaType.GROUP) + @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 \"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) + public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, GroupDto groupDto) { + throw new UnsupportedOperationException("Not yet implemented"); } } 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/ResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceManagerAdapter.java index 2bc2193561..ee4e105a51 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 @@ -16,11 +16,17 @@ import java.util.function.Supplier; /** * 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); 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..61e71fd10e 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; @@ -45,7 +46,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 @@ -58,7 +59,8 @@ public class UserCollectionResource { @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/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java index 07008e8e4c..a7a6bcc28f 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; @@ -38,7 +39,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 @@ -49,7 +50,8 @@ public class UserResource { @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; From 38a32e4e2a469597e255e35a9208f7a17c9ef073 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 27 Jun 2018 14:38:39 +0200 Subject: [PATCH 6/8] Assimilates User and Group REST Resource Annotations and docs some more --- .../sonia/scm/api/v2/resources/GroupCollectionResource.java | 2 +- .../java/sonia/scm/api/v2/resources/UserCollectionResource.java | 2 +- .../src/main/java/sonia/scm/api/v2/resources/UserResource.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 c0c8079b57..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 @@ -26,7 +26,6 @@ import java.io.IOException; import static sonia.scm.api.v2.resources.ResourceLinks.group; -@Produces(VndMediaType.GROUP_COLLECTION) public class GroupCollectionResource { private static final int DEFAULT_PAGE_SIZE = 10; @@ -55,6 +54,7 @@ public class GroupCollectionResource { */ @GET @Path("") + @Produces(VndMediaType.GROUP_COLLECTION) @TypeHint(GroupDto[].class) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), 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 61e71fd10e..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 @@ -26,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; @@ -56,6 +55,7 @@ public class UserCollectionResource { */ @GET @Path("") + @Produces(VndMediaType.USER_COLLECTION) @TypeHint(UserDto[].class) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), 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 a7a6bcc28f..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 @@ -21,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; @@ -47,6 +46,7 @@ public class UserResource { */ @GET @Path("") + @Produces(VndMediaType.USER) @TypeHint(UserDto.class) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), From c5cafb8b6b2f35b8f4f48f6a85e892f3eb1104c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Jun 2018 14:38:46 +0200 Subject: [PATCH 7/8] Simplify test --- .../java/sonia/scm/api/v2/resources/UserRootResourceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e5f0ae789a..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 @@ -72,7 +72,6 @@ public class UserRootResourceTest { initMocks(this); 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()); @@ -197,7 +196,7 @@ public class UserRootResourceTest { public void shouldFailUpdateForDifferentIds() throws IOException, URISyntaxException, UserException { URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json"); byte[] userJson = Resources.toByteArray(url); - when(userManager.get("Other")).thenReturn(createDummyUser("Other")); + createDummyUser("Other"); MockHttpRequest request = MockHttpRequest .put("/" + UserRootResource.USERS_PATH_V2 + "Other") @@ -234,6 +233,7 @@ public class UserRootResourceTest { user.setName(name); user.setPassword("redpill"); user.setCreationDate(System.currentTimeMillis()); + when(userManager.get(name)).thenReturn(user); return user; } } From afd4b06b618ca1ffb8eeb0bf2b23e3a007d363ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Jun 2018 15:00:52 +0200 Subject: [PATCH 8/8] Remove not used functions Equals and hashCode are not used, yet. Therefore we have no idea, how they should be implemented at all. So they should not be implemented, yet. Further they are polluting the test coverage. --- .../java/sonia/scm/api/v2/resources/CollectionDto.java | 6 +++--- .../main/java/sonia/scm/api/v2/resources/GroupDto.java | 6 +++--- .../main/java/sonia/scm/api/v2/resources/MemberDto.java | 7 +++---- .../src/main/java/sonia/scm/api/v2/resources/UserDto.java | 8 +++----- 4 files changed, 12 insertions(+), 15 deletions(-) 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/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/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/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;