diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollection2DtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollection2DtoMapper.java new file mode 100644 index 0000000000..7d4c049f77 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollection2DtoMapper.java @@ -0,0 +1,64 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.Links; +import de.otto.edison.hal.paging.NumberedPaging; +import de.otto.edison.hal.paging.PagingRel; +import sonia.scm.PageResult; +import sonia.scm.user.User; +import sonia.scm.user.UserPermissions; + +import javax.inject.Inject; +import javax.ws.rs.core.UriInfo; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; + +import static com.damnhandy.uri.template.UriTemplate.fromTemplate; +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; +import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging; + +public class UserCollection2DtoMapper { + + private final User2UserDtoMapper userToDtoMapper; + + @Inject + public UserCollection2DtoMapper(User2UserDtoMapper userToDtoMapper) { + this.userToDtoMapper = userToDtoMapper; + } + + public UserCollectionDto userCollectionToUserDto(UriInfo uriInfo, int pageNumber, int pageSize, PageResult pageResult) { + NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.hasMore()); + List dtos = pageResult.getEntities().stream().map(user -> userToDtoMapper.userToUserDto(user, uriInfo)).collect(Collectors.toList()); + + UserCollectionDto userCollectionDto = new UserCollectionDto( + createLinks(uriInfo, paging), + embedDtos(dtos) + ); + userCollectionDto.setPage(pageNumber); + return userCollectionDto; + } + + private static Links createLinks(UriInfo uriInfo, NumberedPaging page) { + LinkBuilder collectionLinkBuilder = new LinkBuilder(uriInfo, UserV2Resource.class, UserCollectionResource.class); + String baseUrl = collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").parameters().href(); + + Links.Builder linksBuilder = linkingTo() + .with(page.links( + fromTemplate(baseUrl + "{?page,pageSize}"), + EnumSet.allOf(PagingRel.class))); + if (UserPermissions.create().isPermitted()) { + linksBuilder + .single(link("create", collectionLinkBuilder. method("getUserCollectionResource").parameters().method("create").parameters().href())); + } + return linksBuilder.build(); + } + + private Embedded embedDtos(List dtos) { + return embeddedBuilder() + .with("users", dtos) + .build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionDto.java index 6bd955c417..96b41dae94 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionDto.java @@ -1,27 +1,17 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.paging.NumberedPaging; -import de.otto.edison.hal.paging.PagingRel; - -import java.util.EnumSet; -import java.util.List; - -import static com.damnhandy.uri.template.UriTemplate.fromTemplate; -import static de.otto.edison.hal.Embedded.embeddedBuilder; -import static de.otto.edison.hal.Links.linkingTo; +import de.otto.edison.hal.Links; +import lombok.Data; +@Data public class UserCollectionDto extends HalRepresentation { - public UserCollectionDto(String baseUrl, NumberedPaging page, List users) { - super( - linkingTo() - .with(page.links( - fromTemplate(baseUrl + "{?page,pageSize}"), - EnumSet.allOf(PagingRel.class))) - .build(), - embeddedBuilder() - .with("users", users) - .build() - ); + + private int page; + private int pageTotal; + + public UserCollectionDto(Links links, Embedded embedded) { + super(links, embedded); } } 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 f26c90b957..715c8bc752 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 @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import com.google.inject.Inject; -import com.google.inject.Singleton; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.ResponseHeader; import com.webcohesion.enunciate.metadata.rs.StatusCodes; @@ -17,12 +16,7 @@ import javax.ws.rs.*; import javax.ws.rs.core.*; import java.io.IOException; import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; -import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging; - -@Singleton @Produces(VndMediaType.USER) public class UserCollectionResource extends AbstractManagerResource { public static final int DEFAULT_PAGE_SIZE = 10; @@ -55,19 +49,15 @@ public class UserCollectionResource extends AbstractManagerResource pageResult = fetchPage(sortby, desc, page, pageSize); - LinkBuilder collectionLinkBuilder = new LinkBuilder(uriInfo, UserV2Resource.class, UserCollectionResource.class); - String baseUrl = collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").parameters().href(); - - List dtos = pageResult.getEntities().stream().map(user -> userToDtoMapper.userToUserDto(user, uriInfo)).collect(Collectors.toList()); - - return Response.ok(new UserCollectionDto(baseUrl, zeroBasedNumberedPaging(page, pageSize, pageResult.hasMore()), dtos)).build(); + return Response.ok(new UserCollection2DtoMapper(userToDtoMapper).userCollectionToUserDto(uriInfo, page, pageSize, pageResult)).build(); } @POST diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserSubResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserSubResource.java index 3f5a1752b5..53af57048d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserSubResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserSubResource.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import com.google.inject.Inject; -import com.google.inject.Singleton; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; @@ -18,7 +17,6 @@ import javax.ws.rs.core.*; import java.util.Collection; -@Singleton @Produces(VndMediaType.USER) public class UserSubResource extends AbstractManagerResource { private final UserDto2UserMapper dtoToUserMapper; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserV2Resource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserV2Resource.java index 6da7937b35..4df940147c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserV2Resource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserV2Resource.java @@ -1,11 +1,9 @@ package sonia.scm.api.v2.resources; import com.google.inject.Inject; -import com.google.inject.Singleton; import javax.ws.rs.Path; -@Singleton @Path(UserV2Resource.USERS_PATH_V2) public class UserV2Resource { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollection2DtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollection2DtoMapperTest.java new file mode 100644 index 0000000000..c70a920ad6 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollection2DtoMapperTest.java @@ -0,0 +1,121 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +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.junit.Before; +import org.junit.Test; +import sonia.scm.PageResult; +import sonia.scm.user.User; + +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UserCollection2DtoMapperTest { + + private final UriInfo uriInfo = mock(UriInfo.class); + private final User2UserDtoMapper userToDtoMapper = mock(User2UserDtoMapper.class); + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private final UserCollection2DtoMapper mapper = new UserCollection2DtoMapper(userToDtoMapper); + + private URI expectedBaseUri; + + @Before + public void init() throws URISyntaxException { + URI baseUri = new URI("http://example.com/base/"); + expectedBaseUri = baseUri.resolve(UserV2Resource.USERS_PATH_V2 + "/"); + when(uriInfo.getBaseUri()).thenReturn(baseUri); + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @Test + public void shouldSetPageNumber() { + PageResult pageResult = mockPageResult(true, "Hannes"); + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 1, pageResult); + assertEquals(1, userCollectionDto.getPage()); + } + + @Test + public void shouldHaveSelfLink() { + PageResult pageResult = mockPageResult(true, "Hannes"); + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 1, pageResult); + assertTrue(userCollectionDto.getLinks().getLinkBy("self").get().getHref().startsWith(expectedBaseUri.toString())); + } + + @Test + public void shouldCreateNextPageLink_whenHasMore() { + PageResult pageResult = mockPageResult(true, "Hannes"); + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 1, pageResult); + assertTrue(userCollectionDto.getLinks().getLinkBy("next").get().getHref().contains("page=2")); + } + + @Test + public void shouldNotCreateNextPageLink_whenNoMore() { + PageResult pageResult = mockPageResult(false, "Hannes"); + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 1, pageResult); + assertFalse(userCollectionDto.getLinks().stream().anyMatch(link -> link.getHref().contains("page=2"))); + } + + @Test + public void shouldHaveCreateLink_whenHasPermission() { + PageResult pageResult = mockPageResult(false, "Hannes"); + when(subject.isPermitted("user:create")).thenReturn(true); + + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 1, pageResult); + + assertTrue(userCollectionDto.getLinks().getLinkBy("create").isPresent()); + } + + @Test + public void shouldNotHaveCreateLink_whenHasNoPermission() { + PageResult pageResult = mockPageResult(false, "Hannes"); + when(subject.isPermitted("user:create")).thenReturn(false); + + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 1, pageResult); + + assertFalse(userCollectionDto.getLinks().getLinkBy("create").isPresent()); + } + + @Test + public void shouldMapUsers() { + PageResult pageResult = mockPageResult(false, "Hannes", "Wurst"); + UserCollectionDto userCollectionDto = mapper.userCollectionToUserDto(uriInfo, 1, 2, pageResult); + List users = userCollectionDto.getEmbedded().getItemsBy("users"); + assertEquals(2, users.size()); + assertEquals("Hannes", ((UserDto) users.get(0)).getName()); + assertEquals("Wurst", ((UserDto) users.get(1)).getName()); + } + + private PageResult mockPageResult(boolean hasMore, String... userNames) { + Collection users = Arrays.stream(userNames).map(this::mockUserWithDto).collect(toList()); + return new PageResult<>(users, hasMore); + } + + private User mockUserWithDto(String userName) { + User user = new User(); + user.setName(userName); + when(userToDtoMapper.userToUserDto(user, uriInfo)).thenReturn(createUserDto(user)); + return user; + } + + private UserDto createUserDto(User user) { + UserDto dto = new UserDto(); + dto.setName(user.getName()); + return dto; + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserV2ResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserV2ResourceTest.java index 75ecb313f8..9b41b5cea3 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserV2ResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserV2ResourceTest.java @@ -9,6 +9,7 @@ import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import sonia.scm.PageResult; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -17,6 +18,8 @@ import java.net.URISyntaxException; import java.util.Collections; import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -35,7 +38,7 @@ public class UserV2ResourceTest { @Before public void prepareEnvironment() { UserManager userManager = mock(UserManager.class); - when(userManager.getAll()).thenReturn(Collections.singletonList(createDummyUser())); + when(userManager.getPage(any(), eq(0), eq(10))).thenReturn(new PageResult<>(Collections.singletonList(createDummyUser()), true)); UserDto2UserMapperImpl dtoToUserMapper = new UserDto2UserMapperImpl(); User2UserDtoMapperImpl userToDtoMapper = new User2UserDtoMapperImpl();