add autocomplete endpoint

This commit is contained in:
Mohamed Karray
2018-10-08 13:39:33 +02:00
parent 0f08f2f7d5
commit ddcc21c1a8
27 changed files with 664 additions and 14 deletions

View File

@@ -114,4 +114,13 @@ public interface GenericDAO<T>
* @return all items * @return all items
*/ */
public Collection<T> getAll(); public Collection<T> getAll();
/**
* Returns items containing the searched string
*
* @param searched the search character
* @param limit the max count of the result entities. if limit is <= 0 return all filtered entities
* @return searched items
*/
Collection<T> getFiltered(String searched, int limit);
} }

View File

@@ -77,6 +77,15 @@ public interface Manager<T extends ModelObject>
*/ */
Collection<T> getAll(); Collection<T> getAll();
/**
* Returns a {@link java.util.Collection} of filtered objects
*
* @param filter the searched string
* @param limit the max count of the result entities. if limit is <= 0 return all filtered entities
* @return all object in the store
*/
Collection<T> getFiltered(String filter, int limit);
/** /**
* Returns all object of the store sorted by the given {@link java.util.Comparator} * Returns all object of the store sorted by the given {@link java.util.Comparator}
* *

View File

@@ -91,6 +91,11 @@ public class ManagerDecorator<T extends ModelObject> implements Manager<T> {
decorated.refresh(object); decorated.refresh(object);
} }
@Override
public Collection<T> getFiltered(String filter, int limit) {
return decorated.getFiltered(filter, limit);
}
@Override @Override
public T get(String id) public T get(String id)
{ {

View File

@@ -44,7 +44,7 @@ import java.io.Serializable;
*/ */
public interface ModelObject public interface ModelObject
extends TypedObject, LastModifiedAware, Cloneable, Validateable, extends TypedObject, LastModifiedAware, Cloneable, Validateable,
Serializable Serializable, ReducedModelObject
{ {
/** /**

View File

@@ -0,0 +1,15 @@
package sonia.scm;
/**
* This is a reduced form of a model object.
* It can be used as search result to avoid returning the whole object properties.
*
* @author Mohamed Karray
*/
public interface ReducedModelObject {
String getId();
String getDisplayName();
}

View File

@@ -60,7 +60,7 @@ import java.util.List;
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@StaticPermissions(value = "group", globalPermissions = {"create", "list"}) @StaticPermissions(value = "group", globalPermissions = {"create", "list", "autocomplete"})
@XmlRootElement(name = "groups") @XmlRootElement(name = "groups")
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class Group extends BasicPropertiesAware public class Group extends BasicPropertiesAware
@@ -309,6 +309,11 @@ public class Group extends BasicPropertiesAware
return name; return name;
} }
@Override
public String getDisplayName() {
return description;
}
/** /**
* Returns a timestamp of the last modified date of this group. * Returns a timestamp of the last modified date of this group.
* *

View File

@@ -256,6 +256,11 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
return id; return id;
} }
@Override
public String getDisplayName() {
return id;
}
@Override @Override
public void setLastModified(Long timestamp) { public void setLastModified(Long timestamp) {
throw new UnsupportedOperationException("changesets are immutable"); throw new UnsupportedOperationException("changesets are immutable");

View File

@@ -60,11 +60,12 @@ import java.util.List;
*/ */
@StaticPermissions( @StaticPermissions(
value = "repository", value = "repository",
permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"} permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"},
globalPermissions = {"create", "autocomplete"}
) )
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "repositories") @XmlRootElement(name = "repositories")
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject { public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{
private static final long serialVersionUID = 3486560714961909711L; private static final long serialVersionUID = 3486560714961909711L;
@@ -183,6 +184,11 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
return id; return id;
} }
@Override
public String getDisplayName() {
return getNamespaceAndName().toString();
}
@Override @Override
public Long getLastModified() { public Long getLastModified() {
return lastModified; return lastModified;

View File

@@ -55,7 +55,7 @@ import java.security.Principal;
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@StaticPermissions(value = "user", globalPermissions = {"create", "list"}) @StaticPermissions(value = "user", globalPermissions = {"create", "list", "autocomplete"})
@XmlRootElement(name = "users") @XmlRootElement(name = "users")
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject

View File

@@ -18,6 +18,7 @@ public class VndMediaType {
public static final String INDEX = PREFIX + "index" + SUFFIX; public static final String INDEX = PREFIX + "index" + SUFFIX;
public static final String USER = PREFIX + "user" + SUFFIX; public static final String USER = PREFIX + "user" + SUFFIX;
public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String GROUP = PREFIX + "group" + SUFFIX;
public static final String AUTOCOMPLETE = PREFIX + "autocomplete" + SUFFIX;
public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; public static final String REPOSITORY = PREFIX + "repository" + SUFFIX;
public static final String PERMISSION = PREFIX + "permission" + SUFFIX; public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;

View File

@@ -78,6 +78,11 @@ public class ManagerTest {
return IntStream.range(0, givenItemCount).boxed().collect(toList()); return IntStream.range(0, givenItemCount).boxed().collect(toList());
} }
@Override
public Collection getFiltered(String filter, int limit) {
return null;
}
@Override @Override
public Collection getAll(Comparator comparator) { return getAll(); } public Collection getAll(Comparator comparator) { return getAll(); }

View File

@@ -41,11 +41,13 @@ import org.slf4j.LoggerFactory;
import sonia.scm.GenericDAO; import sonia.scm.GenericDAO;
import sonia.scm.ModelObject; import sonia.scm.ModelObject;
import sonia.scm.group.xml.XmlGroupDAO; import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.store.ConfigurationStore;
//~--- JDK imports ------------------------------------------------------------ import sonia.scm.util.AssertUtil;
import java.util.Collection; import java.util.Collection;
import sonia.scm.store.ConfigurationStore; import java.util.stream.Collectors;
//~--- JDK imports ------------------------------------------------------------
/** /**
* *
@@ -234,6 +236,16 @@ public abstract class AbstractXmlDAO<I extends ModelObject,
return ImmutableList.copyOf(db.values()); return ImmutableList.copyOf(db.values());
} }
@Override
public Collection<I> getFiltered(String searched, int limit) {
int size = db.values().size();
AssertUtil.assertIsNotEmpty(searched);
return ImmutableList.copyOf(db.values().stream()
.filter(item -> item.getId().contains(searched) || (item.getDisplayName() != null && item.getDisplayName().contains(searched)))
.limit(limit <= 0 ? size : limit)
.collect(Collectors.toList()));
}
/** /**
* Method description * Method description
* *

View File

@@ -0,0 +1,11 @@
package sonia.scm.api.v2.resources;
public class AutoCompleteBadParamException extends Exception {
public static final String PARAMETER_IS_REQUIRED = "The parameter is required.";
public static final String INVALID_PARAMETER_LENGTH = "Invalid parameter length.";
public AutoCompleteBadParamException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,16 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class AutoCompleteBadParamExceptionMapper implements ExceptionMapper<AutoCompleteBadParamException> {
@Override
public Response toResponse(AutoCompleteBadParamException exception) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(exception.getMessage())
.build();
}
}

View File

@@ -0,0 +1,113 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import org.apache.commons.lang.StringUtils;
import sonia.scm.group.GroupManager;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.util.stream.Collectors;
import static sonia.scm.api.v2.resources.AutoCompleteBadParamException.INVALID_PARAMETER_LENGTH;
import static sonia.scm.api.v2.resources.AutoCompleteBadParamException.PARAMETER_IS_REQUIRED;
@Path(AutoCompleteResource.PATH)
public class AutoCompleteResource {
public static final String PATH = "v2/autocomplete/";
public static final String DEFAULT_LIMIT = "5";
public static final int MIN_SEARCHED_CHARS = 1;
private ReducedObjectModelToDtoMapper mapper;
private UserManager userManager;
private GroupManager groupManager;
private RepositoryManager repositoryManager;
@Inject
public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserManager userManager, GroupManager groupManager, RepositoryManager repositoryManager) {
this.mapper = mapper;
this.userManager = userManager;
this.groupManager = groupManager;
this.repositoryManager = repositoryManager;
}
@GET
@Path("user")
@Produces(VndMediaType.AUTOCOMPLETE)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@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(@QueryParam("filter") String filter,
@DefaultValue(DEFAULT_LIMIT) @QueryParam("limit") Integer limit) throws AutoCompleteBadParamException {
validateParams(filter);
return Response.ok(userManager.getFiltered(filter, limit)
.stream()
.map(mapper::map)
.collect(Collectors.toList()))
.build();
}
@GET
@Path("group")
@Produces(VndMediaType.AUTOCOMPLETE)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@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(@Valid @QueryParam("filter") String filter,
@DefaultValue(DEFAULT_LIMIT) @QueryParam("limit") Integer limit) throws AutoCompleteBadParamException {
validateParams(filter);
return Response.ok(groupManager.getFiltered(filter, limit)
.stream()
.map(mapper::map)
.collect(Collectors.toList()))
.build();
}
@GET
@Path("repository")
@Produces(VndMediaType.AUTOCOMPLETE)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository:autocomplete\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response searchRepo(@Valid @QueryParam("filter") String filter,
@DefaultValue(DEFAULT_LIMIT) @QueryParam("limit") Integer limit) throws AutoCompleteBadParamException {
validateParams(filter);
return Response.ok(repositoryManager.getFiltered(filter, limit)
.stream()
.map(mapper::map)
.collect(Collectors.toList()))
.build();
}
void validateParams(String filter) throws AutoCompleteBadParamException {
if (StringUtils.isBlank(filter)) {
throw new AutoCompleteBadParamException(PARAMETER_IS_REQUIRED);
}
if (filter.length() <= MIN_SEARCHED_CHARS) {
throw new AutoCompleteBadParamException(INVALID_PARAMETER_LENGTH);
}
}
}

View File

@@ -37,6 +37,8 @@ public class MapperModule extends AbstractModule {
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass()); bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass()); bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass());
// no mapstruct required // no mapstruct required
bind(UIPluginDtoMapper.class); bind(UIPluginDtoMapper.class);
bind(UIPluginDtoCollectionMapper.class); bind(UIPluginDtoCollectionMapper.class);

View File

@@ -0,0 +1,16 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class ReducedObjectModelDto extends HalRepresentation {
private String id;
private String displayName;
}

View File

@@ -0,0 +1,13 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import sonia.scm.ReducedModelObject;
@Mapper
public abstract class ReducedObjectModelToDtoMapper {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract ReducedObjectModelDto map(ReducedModelObject modelObject);
}

View File

@@ -242,6 +242,11 @@ public class DefaultGroupManager extends AbstractGroupManager
return group; return group;
} }
@Override
public Collection<Group> getFiltered(String filter, int limit) {
GroupPermissions.autocomplete().check();
return groupDAO.getFiltered(filter, limit);
}
/** /**
* Method description * Method description
* *

View File

@@ -243,6 +243,12 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repository; return repository;
} }
@Override
public Collection<Repository> getFiltered(String filter, int limit) {
RepositoryPermissions.autocomplete().check();
return repositoryDAO.getFiltered(filter, limit);
}
@Override @Override
public Collection<Repository> getAll(Comparator<Repository> comparator) { public Collection<Repository> getAll(Comparator<Repository> comparator) {
List<Repository> repositories = Lists.newArrayList(); List<Repository> repositories = Lists.newArrayList();

View File

@@ -52,9 +52,11 @@ import org.slf4j.LoggerFactory;
import sonia.scm.cache.Cache; import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager; import sonia.scm.cache.CacheManager;
import sonia.scm.group.GroupNames; import sonia.scm.group.GroupNames;
import sonia.scm.group.GroupPermissions;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserPermissions; import sonia.scm.user.UserPermissions;
import sonia.scm.util.Util; import sonia.scm.util.Util;
@@ -256,6 +258,9 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
collectGlobalPermissions(builder, user, groups); collectGlobalPermissions(builder, user, groups);
collectRepositoryPermissions(builder, user, groups); collectRepositoryPermissions(builder, user, groups);
builder.add(canReadOwnUser(user)); builder.add(canReadOwnUser(user));
builder.add(getUserAutocompletePermission());
builder.add(getGroupAutocompletePermission());
builder.add(getRepoAutocompletePermission());
permissions = builder.build(); permissions = builder.build();
} }
@@ -264,6 +269,18 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
return info; return info;
} }
private String getRepoAutocompletePermission() {
return RepositoryPermissions.autocomplete().asShiroString();
}
private String getGroupAutocompletePermission() {
return GroupPermissions.autocomplete().asShiroString();
}
private String getUserAutocompletePermission() {
return UserPermissions.autocomplete().asShiroString();
}
private String canReadOwnUser(User user) { private String canReadOwnUser(User user) {
return UserPermissions.read(user.getName()).asShiroString(); return UserPermissions.read(user.getName()).asShiroString();
} }

View File

@@ -300,6 +300,12 @@ public class DefaultUserManager extends AbstractUserManager
return getAll(null); return getAll(null);
} }
@Override
public Collection<User> getFiltered(String filter, int limit) {
UserPermissions.autocomplete().check();
return userDAO.getFiltered(filter, limit);
}
/** /**
* Method description * Method description
* *

View File

@@ -105,6 +105,11 @@ public class AbstractManagerResourceTest {
return id; return id;
} }
@Override
public String getDisplayName() {
return id;
}
@Override @Override
public void setLastModified(Long timestamp) { public void setLastModified(Long timestamp) {

View File

@@ -0,0 +1,368 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.SCMContextProvider;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.DefaultGroupManager;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.repository.DefaultRepositoryManager;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.security.KeyGenerator;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.user.DefaultUserManager;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.user.xml.XmlUserDAO;
import sonia.scm.web.VndMediaType;
import sonia.scm.xml.XmlDatabase;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@RunWith(MockitoJUnitRunner.Silent.class)
public class AutoCompleteResourceTest {
public static final String URL = "/" + AutoCompleteResource.PATH;
private final Integer defaultLimit = Integer.valueOf(AutoCompleteResource.DEFAULT_LIMIT);
private Dispatcher dispatcher;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
private XmlUserDAO userDaoMock;
private XmlGroupDAO groupDaoMock;
private XmlRepositoryDAO repoDaoMock;
private XmlDatabase xmlDB;
private ObjectMapper jsonObjectMapper = new ObjectMapper();
@Before
public void prepareEnvironment() {
initMocks(this);
ConfigurationStoreFactory storeFactory = mock(ConfigurationStoreFactory.class);
ConfigurationStore<Object> storeConfig = mock(ConfigurationStore.class);
xmlDB = mock(XmlDatabase.class);
when(storeConfig.get()).thenReturn(xmlDB);
when(storeFactory.getStore(any(), any())).thenReturn(storeConfig);
XmlUserDAO userDao = new XmlUserDAO(storeFactory);
userDaoMock = spy(userDao);
XmlGroupDAO groupDAO = new XmlGroupDAO(storeFactory);
groupDaoMock = spy(groupDAO);
XmlRepositoryDAO repoDao = new XmlRepositoryDAO(storeFactory);
repoDaoMock = spy(repoDao);
ReducedObjectModelToDtoMapperImpl mapper = new ReducedObjectModelToDtoMapperImpl();
UserManager userManager = new DefaultUserManager(userDaoMock);
GroupManager groupManager = new DefaultGroupManager(groupDaoMock);
RepositoryManager repositoryManager = new DefaultRepositoryManager(mock(ScmConfiguration.class), mock(SCMContextProvider.class), mock(KeyGenerator.class), repoDaoMock, new HashSet<>(), mock(NamespaceStrategy.class));
AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager, repositoryManager);
dispatcher = createDispatcher(autoCompleteResource);
subjectThreadState.bind();
ThreadContext.bind(subject);
when(subject.isPermitted(any(String.class))).thenReturn(true);
}
@After
public void cleanupContext() {
ThreadContext.unbindSubject();
}
@Test
public void shouldGet400OnFailedParameterForUserSearch() throws Exception {
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "user")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldGet400IfParameterLengthLessThan2CharsForUserSearch() throws Exception {
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "user?filter=a")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldSearchUsers() throws Exception {
ArrayList<User> users = Lists.newArrayList(createMockUser("YuCantFindMe", "ha ha"), createMockUser("user1", "User 1"), createMockUser("user2", "User 2"));
String searched = "user";
when(xmlDB.values()).thenReturn(users);
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "user?filter=" + searched)
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertResultSize(response, 2);
assertTrue(response.getContentAsString().contains("\"id\":\"user1\""));
assertTrue(response.getContentAsString().contains("\"displayName\":\"User 1\""));
assertTrue(response.getContentAsString().contains("\"id\":\"user2\""));
assertTrue(response.getContentAsString().contains("\"displayName\":\"User 2\""));
}
@Test
public void shouldSearchUsersWithLimitLength() throws Exception {
List<User> users = IntStream.range(0, 10).boxed().map(i -> createMockUser("user" + i, "User " + i)).collect(Collectors.toList());
when(xmlDB.values()).thenReturn(users);
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "user?filter=user&limit=1")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
verify(userDaoMock).getFiltered(eq("user"), eq(1));
assertResultSize(response, 1);
}
@Test
public void shouldSearchUsersWithDefaultLimitLength() throws Exception {
List<User> 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")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
verify(userDaoMock).getFiltered(eq("user"), eq(defaultLimit));
assertResultSize(response, defaultLimit);
}
@Test
public void shouldGet400OnFailedParameterForGroupSearch() throws Exception {
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "group")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldGet400IfParameterLengthLessThan2CharsForGroupSearch() throws Exception {
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "group?filter=a")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldSearchGroups() throws Exception {
ArrayList<Group> groups = Lists.newArrayList(createMockGroup("YuCantFindMe"), createMockGroup("group_1"), createMockGroup("group_2"));
String searched = "group";
when(xmlDB.values()).thenReturn(groups);
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "group?filter=" + searched)
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertResultSize(response, 2);
assertTrue(response.getContentAsString().contains("\"id\":\"group_1\""));
assertTrue(response.getContentAsString().contains("\"displayName\":\"group_1\""));
assertTrue(response.getContentAsString().contains("\"id\":\"group_2\""));
assertTrue(response.getContentAsString().contains("\"displayName\":\"group_2\""));
}
@Test
public void shouldSearchGroupsWithLimitLength() throws Exception {
List<Group> 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&limit=1")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
verify(groupDaoMock).getFiltered(eq("group"), eq(1));
assertResultSize(response, 1);
}
@Test
public void shouldSearchGroupsWithDefaultLimitLength() throws Exception {
List<Group> 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")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
verify(groupDaoMock).getFiltered(eq("group"), eq(defaultLimit));
assertResultSize(response, defaultLimit);
}
@Test
public void shouldGet400OnFailedParameterForRepoSearch() throws Exception {
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "repository")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldGet400IfParameterLengthLessThan2CharsForRepoSearch() throws Exception {
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "repository?filter=a")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldSearchRepos() throws Exception {
List<Repository> repos = Lists.newArrayList(createMockRepo("YCannotFindMe"), createMockRepo("repo1"), createMockRepo("repo2"));
when(xmlDB.values()).thenReturn(repos);
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "repository?filter=repo")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertResultSize(response, 2);
assertTrue(response.getContentAsString().contains("\"displayName\":\"space/repo1\""));
assertTrue(response.getContentAsString().contains("\"displayName\":\"space/repo2\""));
}
@Test
public void shouldSearchReposWithLimitLength() throws Exception {
List<Repository> repositories = IntStream.range(0, 10).boxed().map(i -> createMockRepo("repo" + i)).collect(Collectors.toList());
when(xmlDB.values()).thenReturn(repositories);
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "repository?filter=repo&limit=1")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
verify(repoDaoMock).getFiltered(eq("repo"), eq(1));
assertResultSize(response, 1);
}
@Test
public void shouldSearchReposWithDefaultLimitLength() throws Exception {
List<Repository> repositories = IntStream.range(0, 10).boxed().map(i -> createMockRepo("repo" + i)).collect(Collectors.toList());
when(xmlDB.values()).thenReturn(repositories);
MockHttpRequest request = MockHttpRequest
.get("/" + AutoCompleteResource.PATH + "repository?filter=repo")
.contentType(VndMediaType.AUTOCOMPLETE)
.accept(VndMediaType.AUTOCOMPLETE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
verify(repoDaoMock).getFiltered(eq("repo"), eq(defaultLimit));
assertResultSize(response, defaultLimit);
}
private User createMockUser(String id, String name) {
return new User(id, name, "em@l.de");
}
private Group createMockGroup(String name) {
Group group = new Group("type", name);
group.setDescription(name);
return group;
}
private Repository createMockRepo(String repository) {
return new Repository("id", "git", "space", repository);
}
private void assertResultSize(MockHttpResponse response, int size) throws java.io.IOException {
ReducedObjectModelDto[] reducedObjectModelDtos = jsonObjectMapper.readValue(response.getContentAsString(), ReducedObjectModelDto[].class);
assertTrue(reducedObjectModelDtos.length == size);
}
}

View File

@@ -17,6 +17,7 @@ public class DispatcherMock {
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class);
dispatcher.getProviderFactory().registerProvider(InvalidPasswordExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(InvalidPasswordExceptionMapper.class);
dispatcher.getProviderFactory().registerProvider(AutoCompleteBadParamExceptionMapper.class);
return dispatcher; return dispatcher;
} }
} }

View File

@@ -23,7 +23,6 @@ import javax.servlet.http.HttpServletResponse;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.text.MessageFormat;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;

View File

@@ -161,8 +161,8 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect(); AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.contains(Role.USER)); assertThat(authInfo.getRoles(), Matchers.contains(Role.USER));
assertThat(authInfo.getStringPermissions(), hasSize(1)); assertThat(authInfo.getStringPermissions(), hasSize(4));
assertThat(authInfo.getStringPermissions(), contains("user:read:trillian")); assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "repository:autocomplete", "user:read:trillian"));
assertThat(authInfo.getObjectPermissions(), nullValue()); assertThat(authInfo.getObjectPermissions(), nullValue());
} }
@@ -209,7 +209,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect(); AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER)); assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue()); assertThat(authInfo.getObjectPermissions(), nullValue());
assertThat(authInfo.getStringPermissions(), containsInAnyOrder("repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian")); assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete","repository:autocomplete", "repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian"));
} }
/** /**
@@ -230,7 +230,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect(); AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER)); assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue()); assertThat(authInfo.getObjectPermissions(), nullValue());
assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two", "user:read:trillian")); assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two", "user:read:trillian", "user:autocomplete" , "group:autocomplete", "repository:autocomplete"));
} }
private void authenticate(User user, String group, String... groups) { private void authenticate(User user, String group, String... groups) {