diff --git a/scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java b/scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java new file mode 100644 index 0000000000..a0579eee6f --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java @@ -0,0 +1,103 @@ +package sonia.scm.it; + +import org.junit.Before; +import org.junit.Test; +import sonia.scm.it.utils.ScmRequests; +import sonia.scm.it.utils.TestData; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AutoCompleteITCase { + + + public static final String CREATED_USER_PREFIX = "user"; + public static final String CREATED_GROUP_PREFIX = "group_"; + + @Before + public void init() { + TestData.cleanup(); + } + + @Test + public void adminShouldAutoCompleteUsers() { + createUsers(); + ScmRequests.start() + .given() + .url(TestData.getUsersAutoCompleteUrl("user*")) + .usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN) + .getAutoCompleteResource() + .assertStatusCode(200) + .usingAutoCompleteResponse() + .assertAutoCompleteResults(assertAutoCompleteResult(CREATED_USER_PREFIX)); + } + + @Test + public void userShouldAutoCompleteUsersWithoutAdminPermission() { + String username = "nonAdmin"; + String password = "pass"; + TestData.createUser(username, password, false, "xml", "email@e.de"); + createUsers(); + ScmRequests.start() + .given() + .url(TestData.getUsersAutoCompleteUrl("user*")) + .usernameAndPassword(username, password) + .getAutoCompleteResource() + .assertStatusCode(200) + .usingAutoCompleteResponse() + .assertAutoCompleteResults(assertAutoCompleteResult(CREATED_USER_PREFIX)); + } + + @Test + public void adminShouldAutoCompleteGroups() { + createGroups(); + ScmRequests.start() + .given() + .url(TestData.getGroupsAutoCompleteUrl("group*")) + .usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN) + .getAutoCompleteResource() + .assertStatusCode(200) + .usingAutoCompleteResponse() + .assertAutoCompleteResults(assertAutoCompleteResult(CREATED_GROUP_PREFIX)); + } + + @Test + public void userShouldAutoCompleteGroupsWithoutAdminPermission() { + String username = "nonAdminUser"; + String password = "pass"; + TestData.createNotAdminUser(username, password); + createGroups(); + ScmRequests.start() + .given() + .url(TestData.getGroupsAutoCompleteUrl("group*")) + .usernameAndPassword(username, password) + .getAutoCompleteResource() + .assertStatusCode(200) + .usingAutoCompleteResponse() + .assertAutoCompleteResults(assertAutoCompleteResult(CREATED_GROUP_PREFIX)); + } + + @SuppressWarnings("unchecked") + private Consumer> assertAutoCompleteResult(String id) { + return autoCompleteDtos -> { + IntStream.range(0, 5).forEach(i -> { + assertThat(autoCompleteDtos).as("return maximum 5 entries").hasSize(5); + assertThat(autoCompleteDtos.get(i)).containsEntry("id", id + (i + 1)); + assertThat(autoCompleteDtos.get(i)).containsEntry("displayName", id + (i + 1)); + }); + }; + } + + private void createUsers() { + IntStream.range(0, 6).forEach(i -> TestData.createUser(CREATED_USER_PREFIX + (i + 1), "pass", false, "xml", CREATED_USER_PREFIX + (i + 1) + "@scm-manager.org")); + } + + private void createGroups() { + IntStream.range(0, 6).forEach(i -> TestData.createGroup(CREATED_GROUP_PREFIX + (i + 1), CREATED_GROUP_PREFIX + (i + 1))); + } + +} diff --git a/scm-it/src/test/java/sonia/scm/it/MeITCase.java b/scm-it/src/test/java/sonia/scm/it/MeITCase.java index 64f06765ea..5a1df060c8 100644 --- a/scm-it/src/test/java/sonia/scm/it/MeITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/MeITCase.java @@ -49,7 +49,7 @@ public class MeITCase { String newUser = "user"; String password = "pass"; String type = "not XML Type"; - TestData.createUser(newUser, password, true, type); + TestData.createUser(newUser, password, true, type, "user@scm-manager.org"); ScmRequests.start() .given() .url(TestData.getMeUrl()) diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java index f288d4891c..8785f1d8ce 100644 --- a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java @@ -87,13 +87,13 @@ public class PermissionsITCase { @Before public void prepareEnvironment() { TestData.createDefault(); - TestData.createUser(USER_READ, USER_PASS); + TestData.createNotAdminUser(USER_READ, USER_PASS); TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType); - TestData.createUser(USER_WRITE, USER_PASS); + TestData.createNotAdminUser(USER_WRITE, USER_PASS); TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType); - TestData.createUser(USER_OWNER, USER_PASS); + TestData.createNotAdminUser(USER_OWNER, USER_PASS); TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); - TestData.createUser(USER_OTHER, USER_PASS); + TestData.createNotAdminUser(USER_OTHER, USER_PASS); createdPermissions = 3; } diff --git a/scm-it/src/test/java/sonia/scm/it/UserITCase.java b/scm-it/src/test/java/sonia/scm/it/UserITCase.java index 33fbe0cc5d..3b220da00c 100644 --- a/scm-it/src/test/java/sonia/scm/it/UserITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/UserITCase.java @@ -19,7 +19,7 @@ public class UserITCase { public void adminShouldChangeOwnPassword() { String newUser = "user"; String password = "pass"; - TestData.createUser(newUser, password, true, "xml"); + TestData.createUser(newUser, password, true, "xml", "user@scm-manager.org"); String newPassword = "new_password"; // admin change the own password ScmRequests.start() @@ -50,7 +50,7 @@ public class UserITCase { public void adminShouldChangePasswordOfOtherUser() { String newUser = "user"; String password = "pass"; - TestData.createUser(newUser, password, true, "xml"); + TestData.createUser(newUser, password, true, "xml", "user@scm-manager.org"); String newPassword = "new_password"; // admin change the password of the user ScmRequests.start() @@ -80,7 +80,7 @@ public class UserITCase { String newUser = "user"; String password = "pass"; String type = "not XML Type"; - TestData.createUser(newUser, password, true, type); + TestData.createUser(newUser, password, true, type, "user@scm-manager.org"); ScmRequests.start() .given() .url(TestData.getMeUrl()) diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index 41fd9a1290..8b4eb34188 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -154,6 +154,12 @@ public class ScmRequests { applyGETRequest(url) ); } + + public AppliedAutoCompleteRequest getAutoCompleteResource() { + return new AppliedAutoCompleteRequest( + applyGETRequest(url) + ); + } } public class AppliedRequest { @@ -462,4 +468,29 @@ public class ScmRequests { } } + + public class AppliedAutoCompleteRequest extends AppliedRequest { + public AppliedAutoCompleteRequest(Response response) { + super(response); + } + + public AutoCompleteResponse usingAutoCompleteResponse() { + return new AutoCompleteResponse(super.response); + } + + } + + public class AutoCompleteResponse extends ModelResponse{ + + public AutoCompleteResponse(Response response) { + super(response); + } + + public AutoCompleteResponse assertAutoCompleteResults(Consumer> checker){ + List result = response.then().extract().path(""); + checker.accept(result); + return this; + } + + } } diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index 03da80ea3b..af9032e62e 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -46,11 +46,11 @@ public class TestData { return DEFAULT_REPOSITORIES.get(repositoryType); } - public static void createUser(String username, String password) { - createUser(username, password, false, "xml"); + public static void createNotAdminUser(String username, String password) { + createUser(username, password, false, "xml", "user1@scm-manager.org"); } - public static void createUser(String username, String password, boolean isAdmin, String type) { + public static void createUser(String username, String password, boolean isAdmin, String type, final String email) { LOG.info("create user with username: {}", username); String admin = isAdmin ? "true" : "false"; given(VndMediaType.USER) @@ -61,7 +61,7 @@ public class TestData { .append(" \"admin\": ").append(admin).append(",\n") .append(" \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n") .append(" \"displayName\": \"").append(username).append("\",\n") - .append(" \"mail\": \"user1@scm-manager.org\",\n") + .append(" \"mail\": \"" + email + "\",\n") .append(" \"name\": \"").append(username).append("\",\n") .append(" \"password\": \"").append(password).append("\",\n") .append(" \"type\": \"").append(type).append("\"\n") @@ -71,6 +71,16 @@ public class TestData { .statusCode(HttpStatus.SC_CREATED) ; } + public static void createGroup(String groupName, String desc) { + LOG.info("create group with group name: {} and description {}", groupName, desc); + given(VndMediaType.GROUP) + .when() + .content(getGroupJson(groupName,desc)) + .post(getGroupsUrl()) + .then() + .statusCode(HttpStatus.SC_CREATED) + ; + } public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) { String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); @@ -199,21 +209,36 @@ public class TestData { .build().toString(); } + public static String getGroupJson(String groupname , String desc) { + return JSON_BUILDER + .add("name", groupname) + .add("description", desc) + .build().toString(); + } + public static URI getMeUrl() { return RestUtil.createResourceUrl("me/"); + } + public static URI getGroupsUrl() { + return RestUtil.createResourceUrl("groups/"); } public static URI getUsersUrl() { return RestUtil.createResourceUrl("users/"); - } public static URI getUserUrl(String username) { return getUsersUrl().resolve(username); - } + public static URI getUsersAutoCompleteUrl(String query ) { + return RestUtil.createResourceUrl("autocomplete/users?q="+query); + } + + public static URI getGroupsAutoCompleteUrl(String query ) { + return RestUtil.createResourceUrl("autocomplete/groups?q="+query); + } public static String createPasswordChangeJson(String oldPassword, String newPassword) { return JSON_BUILDER @@ -225,4 +250,5 @@ public class TestData { public static void main(String[] args) { cleanup(); } + } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java index 70192973cb..97a1fcccf9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java @@ -41,7 +41,7 @@ public class AutoCompleteResource { } @GET - @Path("user") + @Path("users") @Produces(VndMediaType.AUTOCOMPLETE) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @@ -50,12 +50,12 @@ public class AutoCompleteResource { @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user:autocomplete\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) - public Response searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("filter") String filter) { + public Response searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) { return map(userManager.autocomplete(filter)); } @GET - @Path("group") + @Path("groups") @Produces(VndMediaType.AUTOCOMPLETE) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @@ -64,7 +64,7 @@ public class AutoCompleteResource { @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group:autocomplete\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) - public Response searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("filter") String filter) { + public Response searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) { return map(groupManager.autocomplete(filter)); } diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 327415ec22..bdd51836b9 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -50,6 +50,7 @@ import sonia.scm.SCMContextProvider; import sonia.scm.TransformFilter; import sonia.scm.search.SearchRequest; import sonia.scm.search.SearchUtil; +import sonia.scm.user.UserPermissions; import sonia.scm.util.CollectionAppender; import sonia.scm.util.Util; @@ -244,8 +245,9 @@ public class DefaultGroupManager extends AbstractGroupManager @Override public Collection autocomplete(String filter) { - GroupPermissions.autocomplete().check(); - return search(new SearchRequest(filter,true, DEFAULT_LIMIT)); + UserPermissions.autocomplete().check(); + SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); + return SearchUtil.search(searchRequest, groupDAO.getAll(), group -> matches(searchRequest,group)?group:null); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 19802902b8..21cd4cc319 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -229,6 +229,13 @@ public class DefaultUserManager extends AbstractUserManager fresh.copyProperties(user); } + @Override + public Collection autocomplete(String filter) { + UserPermissions.autocomplete().check(); + SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); + return SearchUtil.search(searchRequest, userDAO.getAll(), user -> matches(searchRequest,user)?user:null); + } + /** * Method description * @@ -258,7 +265,7 @@ public class DefaultUserManager extends AbstractUserManager } }); } - + private boolean matches(SearchRequest searchRequest, User user) { return SearchUtil.matchesOne(searchRequest, user.getName(), user.getDisplayName(), user.getMail()); } @@ -277,7 +284,7 @@ public class DefaultUserManager extends AbstractUserManager public User get(String id) { UserPermissions.read().check(id); - + User user = userDAO.get(id); if (user != null) @@ -300,12 +307,6 @@ public class DefaultUserManager extends AbstractUserManager return getAll(null); } - @Override - public Collection autocomplete(String filter) { - UserPermissions.autocomplete().check(); - return search(new SearchRequest(filter,true, DEFAULT_LIMIT)); - } - /** * Method description * diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java index adc396458c..68bb165140 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java @@ -88,7 +88,7 @@ public class AutoCompleteResourceTest { @Test public void shouldGet400OnFailedParameterForUserSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user") + .get("/" + AutoCompleteResource.PATH + "users") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -101,7 +101,7 @@ public class AutoCompleteResourceTest { @Test public void shouldGet400IfParameterLengthLessThan2CharsForUserSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user?filter=a") + .get("/" + AutoCompleteResource.PATH + "users?q=a") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -117,7 +117,7 @@ public class AutoCompleteResourceTest { String searched = "user"; when(xmlDB.values()).thenReturn(users); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user?filter=" + searched) + .get("/" + AutoCompleteResource.PATH + "users?q=" + searched) .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -138,7 +138,7 @@ public class AutoCompleteResourceTest { List userList = IntStream.range(0, 10).boxed().map(i -> createMockUser("user" + i, "User " + i)).collect(Collectors.toList()); when(xmlDB.values()).thenReturn(userList); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user?filter=user") + .get("/" + AutoCompleteResource.PATH + "users?q=user") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -152,7 +152,7 @@ public class AutoCompleteResourceTest { @Test public void shouldGet400OnFailedParameterForGroupSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group") + .get("/" + AutoCompleteResource.PATH + "groups") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -165,7 +165,7 @@ public class AutoCompleteResourceTest { @Test public void shouldGet400IfParameterLengthLessThan2CharsForGroupSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group?filter=a") + .get("/" + AutoCompleteResource.PATH + "groups?q=a") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -181,7 +181,7 @@ public class AutoCompleteResourceTest { String searched = "group"; when(xmlDB.values()).thenReturn(groups); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group?filter=" + searched) + .get("/" + AutoCompleteResource.PATH + "groups?q=" + searched) .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -201,7 +201,7 @@ public class AutoCompleteResourceTest { List groups = IntStream.range(0, 10).boxed().map(i -> createMockGroup("group_" + i)).collect(Collectors.toList()); when(xmlDB.values()).thenReturn(groups); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group?filter=group") + .get("/" + AutoCompleteResource.PATH + "groups?q=group") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse();