From 65e6bb9bc25ab3d1fbefeb9a63fe5f0ae4a64d12 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 13 Jul 2018 12:57:46 +0200 Subject: [PATCH 01/45] Create new branch From ada3d6679fc2586a1a37b68f552ec4ffcf0a03e3 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Tue, 17 Jul 2018 13:39:55 +0200 Subject: [PATCH 02/45] Implement git config v2 endpoint --- pom.xml | 1 + .../scm/api/v2/resources/LinkBuilder.java | 0 .../scm/api/v2/resources/UriInfoStore.java | 0 .../main/java/sonia/scm/web/VndMediaType.java | 4 +- scm-plugins/pom.xml | 77 +++++++++++++++ scm-plugins/scm-git-plugin/pom.xml | 14 +++ .../scm/api/v2/resources/GitConfigDto.java | 23 +++++ .../GitConfigDtoToGitConfigMapper.java | 11 +++ .../api/v2/resources/GitConfigResource.java | 93 +++++++++++++++++++ .../GitConfigToGitConfigDtoMapper.java | 45 +++++++++ .../scm/api/v2/resources/MapperModule.java | 16 ++++ .../resources/SimpleRepositoryConfigDto.java | 17 ++++ .../java/sonia/scm/repository/GitConfig.java | 5 +- .../java/sonia/scm/web/GitVndMediaType.java | 5 + .../GitConfigDtoToGitConfigMapperTest.java | 35 +++++++ .../GitConfigToGitConfigDtoMapperTest.java | 77 +++++++++++++++ scm-webapp/pom.xml | 1 - 17 files changed, 420 insertions(+), 4 deletions(-) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java (100%) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java (100%) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java diff --git a/pom.xml b/pom.xml index 2416371d48..2f872b60b2 100644 --- a/pom.xml +++ b/pom.xml @@ -492,6 +492,7 @@ 1.19.4 2.8.6 4.0 + 3.1.3.Final 1.2.0 diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index fb80450bc8..c7b8d369bd 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -9,8 +9,8 @@ public class VndMediaType { private static final String VERSION = "2"; private static final String TYPE = "application"; private static final String SUBTYPE_PREFIX = "vnd.scmm-"; - private static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX; - private static final String SUFFIX = "+json;v=" + VERSION; + public static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX; + public static final String SUFFIX = "+json;v=" + VERSION; public static final String USER = PREFIX + "user" + SUFFIX; public static final String GROUP = PREFIX + "group" + SUFFIX; diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 5b61882815..168c5910e4 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -56,6 +56,79 @@ test + + + + com.webcohesion.enunciate + enunciate-core-annotations + ${enunciate.version} + + + + org.mapstruct + mapstruct-jdk8 + ${org.mapstruct.version} + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + provided + + + + de.otto.edison + edison-hal + 2.0.1 + compile + + + + org.projectlombok + lombok + 1.16.18 + provided + + + + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jaxb-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jackson2-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-guice + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-servlet-initializer + ${resteasy.version} + + @@ -112,6 +185,10 @@ + + 2.9.1 + + release diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 7013c237cb..cdf1775bba 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -59,6 +59,20 @@ test + + com.sun.jersey + jersey-client + ${jersey-client.version} + test + + + + com.sun.jersey.contribs + jersey-apache-client + ${jersey-client.version} + test + + diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java new file mode 100644 index 0000000000..2bd2cf755c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java @@ -0,0 +1,23 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.xml.bind.annotation.XmlElement; + +@NoArgsConstructor +@Getter +@Setter +public class GitConfigDto extends SimpleRepositoryConfigDto { + + @XmlElement(name = "gc-expression") + private String gcExpression; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java new file mode 100644 index 0000000000..74ba684a24 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java @@ -0,0 +1,11 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.repository.GitConfig; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class GitConfigDtoToGitConfigMapper { + public abstract GitConfig map(GitConfigDto dto); +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java new file mode 100644 index 0000000000..a0079464cc --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -0,0 +1,93 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import org.apache.shiro.SecurityUtils; +import sonia.scm.repository.GitConfig; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.security.Role; +import sonia.scm.web.GitVndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +@Path(GitConfigResource.GIT_CONFIG_PATH_V2) +public class GitConfigResource { + + static final String GIT_CONFIG_PATH_V2 = "v2/config/repository/git"; + private final GitConfigDtoToGitConfigMapper dtoToConfigMapper; + private final GitConfigToGitConfigDtoMapper configToDtoMapper; + private final GitRepositoryHandler repositoryHandler; + + @Inject + public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper, GitRepositoryHandler repositoryHandler) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.repositoryHandler = repositoryHandler; + } + + /** + * Returns the git config. + */ + @GET + @Path("") + @Produces(GitVndMediaType.GIT_CONFIG) + @TypeHint(GitConfigDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + Response response; + + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + GitConfig config = repositoryHandler.getConfig(); + + if (config == null) { + config = new GitConfig(); + repositoryHandler.setConfig(config); + } + + response = Response.ok(configToDtoMapper.map(config)).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } + + /** + * Modifies the git config. + * + * @param configDto new git configuration as DTO + */ + @PUT + @Path("") + @Consumes(GitVndMediaType.GIT_CONFIG) + @StatusCodes({ + @ResponseCode(code = 201, condition = "update success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(@Context UriInfo uriInfo, GitConfigDto configDto) { + Response response; + + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + repositoryHandler.setConfig(dtoToConfigMapper.map(configDto)); + repositoryHandler.storeConfig(); + response = Response.created(uriInfo.getRequestUri()).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java new file mode 100644 index 0000000000..ceab24d6f7 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java @@ -0,0 +1,45 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.apache.shiro.SecurityUtils; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import sonia.scm.repository.GitConfig; +import sonia.scm.security.Role; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class GitConfigToGitConfigDtoMapper { + + @Inject + private UriInfoStore uriInfoStore; + + public abstract GitConfigDto map(GitConfig config); + + @AfterMapping + void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(self()); + // TODO: ConfigPermissions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + linksBuilder.single(link("update", update())); + } + target.add(linksBuilder.build()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class); + return linkBuilder.method("get").parameters().href(); + } + + private String update() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class); + return linkBuilder.method("update").parameters().href(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java new file mode 100644 index 0000000000..267ecd201e --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -0,0 +1,16 @@ +package sonia.scm.api.v2.resources; + +import com.google.inject.AbstractModule; +import com.google.inject.servlet.ServletScopes; +import org.mapstruct.factory.Mappers; + +public class MapperModule extends AbstractModule { + @Override + protected void configure() { + + bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass()); + bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass()); + + bind(UriInfoStore.class).in(ServletScopes.REQUEST); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java new file mode 100644 index 0000000000..45cb0bd199 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java @@ -0,0 +1,17 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import lombok.Getter; +import lombok.Setter; + +import javax.xml.bind.annotation.XmlElement; +import java.io.File; + +@Getter +@Setter +public abstract class SimpleRepositoryConfigDto extends HalRepresentation { + + private boolean disabled = false; + @XmlElement(name = "repository-directory") + private File repositoryDirectory; +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java index 80fe8907ac..77352c6ca0 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java @@ -55,5 +55,8 @@ public class GitConfig extends SimpleRepositoryConfig { { return gcExpression; } - + + public void setGcExpression(String gcExpression) { + this.gcExpression = gcExpression; + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java new file mode 100644 index 0000000000..f099faf098 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java @@ -0,0 +1,5 @@ +package sonia.scm.web; + +public class GitVndMediaType { + public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX; +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java new file mode 100644 index 0000000000..3b09dff51f --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java @@ -0,0 +1,35 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.GitConfig; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class GitConfigDtoToGitConfigMapperTest { + + @InjectMocks + private GitConfigDtoToGitConfigMapperImpl mapper; + + @Test + public void shouldMapFields() { + GitConfigDto dto = createDefaultDto(); + GitConfig config = mapper.map(dto); + assertEquals("express", config.getGcExpression()); + assertEquals("repository/directory", config.getRepositoryDirectory().getPath()); + assertEquals(false, config.isDisabled()); + } + + private GitConfigDto createDefaultDto() { + GitConfigDto gitConfigDto = new GitConfigDto(); + gitConfigDto.setGcExpression("express"); + gitConfigDto.setDisabled(false); + gitConfigDto.setRepositoryDirectory(new File("repository/directory")); + return gitConfigDto; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java new file mode 100644 index 0000000000..59f7cc4799 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java @@ -0,0 +1,77 @@ +package sonia.scm.api.v2.resources; + +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.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.GitConfig; +import sonia.scm.security.Role; + +import java.io.File; +import java.net.URI; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class GitConfigToGitConfigDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private GitConfigToGitConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + expectedBaseUri = baseUri.resolve(GitConfigResource.GIT_CONFIG_PATH_V2); + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @After + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldMapFields() { + GitConfig config = createConfiguration(); + + when(subject.hasRole(Role.ADMIN)).thenReturn(true); + GitConfigDto dto = mapper.map(config); + + assertEquals("express", dto.getGcExpression()); + assertFalse(dto.isDisabled()); + assertEquals("repository/directory", dto.getRepositoryDirectory().getPath()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + private GitConfig createConfiguration() { + GitConfig config = new GitConfig(); + config.setDisabled(false); + config.setRepositoryDirectory(new File("repository/directory")); + config.setGcExpression("express"); + return config; + } + +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 98c70ff82a..8d91e21025 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -561,7 +561,6 @@ 2.9.1 1.0 0.8.17 - 3.1.3.Final Tomcat e1 javascript:S3827 From 2aa73883eb174cf1bc4fadded38ab0e10d25370d Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Thu, 19 Jul 2018 16:07:38 +0200 Subject: [PATCH 03/45] Remove redundant dependencies --- scm-plugins/scm-git-plugin/pom.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index cdf1775bba..7013c237cb 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -59,20 +59,6 @@ test - - com.sun.jersey - jersey-client - ${jersey-client.version} - test - - - - com.sun.jersey.contribs - jersey-apache-client - ${jersey-client.version} - test - - From 8e1e77aed4aa5cf76e16c452da846947038cd4bd Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Thu, 19 Jul 2018 16:08:38 +0200 Subject: [PATCH 04/45] Fix git config v2 endpoint path --- .../main/java/sonia/scm/api/v2/resources/GitConfigResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index a0079464cc..0e17529ae7 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -18,7 +18,7 @@ import javax.ws.rs.core.UriInfo; @Path(GitConfigResource.GIT_CONFIG_PATH_V2) public class GitConfigResource { - static final String GIT_CONFIG_PATH_V2 = "v2/config/repository/git"; + static final String GIT_CONFIG_PATH_V2 = "v2/config/repositories/git"; private final GitConfigDtoToGitConfigMapper dtoToConfigMapper; private final GitConfigToGitConfigDtoMapper configToDtoMapper; private final GitRepositoryHandler repositoryHandler; From 94b0fdd21220b25c1f3a620e7e331cd90995fa9a Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Thu, 19 Jul 2018 16:09:38 +0200 Subject: [PATCH 05/45] Add Extension annotation to GitConfig MapperModule --- .../src/main/java/sonia/scm/api/v2/resources/MapperModule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 267ecd201e..fc2f456d46 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -3,7 +3,9 @@ package sonia.scm.api.v2.resources; import com.google.inject.AbstractModule; import com.google.inject.servlet.ServletScopes; import org.mapstruct.factory.Mappers; +import sonia.scm.plugin.Extension; +@Extension public class MapperModule extends AbstractModule { @Override protected void configure() { From 7203fe4668ad8629d88b065793d0b0e7fbbe3ca6 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Thu, 19 Jul 2018 16:43:29 +0200 Subject: [PATCH 06/45] Implement tests for GitConfigResource --- .../v2/resources/GitConfigResourceTest.java | 91 +++++++++++++++++++ .../resources/sonia/scm/repository/shiro.ini | 11 +++ 2 files changed, 102 insertions(+) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java new file mode 100644 index 0000000000..3665be0648 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -0,0 +1,91 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.GitConfig; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.store.ConfigurationStoreFactory; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@SubjectAware( + username = "trillian", + password = "secret", + configuration = "classpath:sonia/scm/repository/shiro.ini" +) +@RunWith(MockitoJUnitRunner.class) +public class GitConfigResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = URI.create("/"); + + @InjectMocks + private GitConfigDtoToGitConfigMapperImpl dtoToConfigMapper; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private GitConfigToGitConfigDtoMapperImpl configToDtoMapper; + + @Mock + ConfigurationStoreFactory storeFactory; + + @InjectMocks + private GitRepositoryHandler repositoryHandler; + + @Before + public void prepareEnvironment() { + GitConfig gitConfig = createConfiguration(); + repositoryHandler.setConfig(gitConfig); + GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); + dispatcher.getRegistry().addSingletonResource(gitConfigResource); + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + } + + @Test + public void shouldGetGitConfig() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"disabled\":false")); + //assertTrue(response.getContentAsString().contains("\"repository-directory\":\"repository/directory\"")); + //assertTrue(response.getContentAsString().contains("\"gc-expression\":\"valid Git GC Cron Expression\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config/repositories/git")); + assertTrue(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/repositories/git")); + } + + private GitConfig createConfiguration() { + GitConfig config = new GitConfig(); + //config.setGcExpression("valid Git GC Cron Expression"); + config.setDisabled(false); + config.setRepositoryDirectory(new File("repository/directory")); + return config; + } + +} + diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini new file mode 100644 index 0000000000..5073bf398d --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini @@ -0,0 +1,11 @@ +[users] +trillian = secret, admin +dent = secret, creator, heartOfGold, puzzle42 +unpriv = secret +crato = secret, creator + +[roles] +admin = * +creator = repository:create +heartOfGold = "repository:read,modify,delete:hof" +puzzle42 = "repository:read,write:p42" From 02327b55f33488eda0e93de62cc79641f9a45d7d Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 11:42:40 +0200 Subject: [PATCH 07/45] Makes pom.xmls more DRY --- pom.xml | 72 ++++++++++++++++++++++++++++++ scm-plugins/pom.xml | 15 ------- scm-plugins/scm-git-plugin/pom.xml | 18 -------- scm-test/pom.xml | 3 +- scm-webapp/pom.xml | 13 +----- 5 files changed, 75 insertions(+), 46 deletions(-) diff --git a/pom.xml b/pom.xml index 0b751a2908..174fa07079 100644 --- a/pom.xml +++ b/pom.xml @@ -181,6 +181,77 @@ true + + com.webcohesion.enunciate + enunciate-core-annotations + ${enunciate.version} + + + + org.mapstruct + mapstruct-jdk8 + ${org.mapstruct.version} + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + provided + + + + de.otto.edison + edison-hal + 2.0.1 + compile + + + + org.projectlombok + lombok + 1.16.18 + provided + + + + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jaxb-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jackson2-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-guice + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-servlet-initializer + ${resteasy.version} + + @@ -555,6 +626,7 @@ 2.0.1 3.1.3.Final 1.19.4 + 2.9.1 2.8.6 4.0 diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 168c5910e4..70dee9676a 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -61,33 +61,28 @@ com.webcohesion.enunciate enunciate-core-annotations - ${enunciate.version} org.mapstruct mapstruct-jdk8 - ${org.mapstruct.version} org.mapstruct mapstruct-processor - ${org.mapstruct.version} provided de.otto.edison edison-hal - 2.0.1 compile org.projectlombok lombok - 1.16.18 provided @@ -96,37 +91,31 @@ org.jboss.resteasy resteasy-jaxrs - ${resteasy.version} org.jboss.resteasy resteasy-jaxb-provider - ${resteasy.version} org.jboss.resteasy resteasy-jackson2-provider - ${resteasy.version} org.jboss.resteasy resteasy-multipart-provider - ${resteasy.version} org.jboss.resteasy resteasy-guice - ${resteasy.version} org.jboss.resteasy resteasy-servlet-initializer - ${resteasy.version} @@ -185,10 +174,6 @@ - - 2.9.1 - - release diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 7013c237cb..c6ae017d9e 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -9,9 +9,7 @@ 2.0.0-SNAPSHOT - sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT scm-git-plugin smp https://bitbucket.org/sdorra/scm-manager @@ -19,13 +17,6 @@ - - javax.servlet - javax.servlet-api - ${servlet.version} - provided - - sonia.jgit org.eclipse.jgit @@ -50,15 +41,6 @@ 2.6 - - - - sonia.scm - scm-test - 2.0.0-SNAPSHOT - test - - diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 086552e864..7556fa4a80 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -37,7 +37,8 @@ com.github.sdorra shiro-unit - test + + compile diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index b1e46c2e37..538eba4911 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -117,48 +117,42 @@ jackson-datatype-jsr310 ${jackson.version} + org.jboss.resteasy resteasy-jaxrs - ${resteasy.version} org.jboss.resteasy resteasy-jaxb-provider - ${resteasy.version} org.jboss.resteasy resteasy-jackson2-provider - ${resteasy.version} org.jboss.resteasy resteasy-multipart-provider - ${resteasy.version} org.jboss.resteasy resteasy-guice - ${resteasy.version} org.jboss.resteasy resteasy-servlet-initializer - ${resteasy.version} de.otto.edison edison-hal - 2.0.1 @@ -261,7 +255,6 @@ com.webcohesion.enunciate enunciate-core-annotations - ${enunciate.version} @@ -387,20 +380,17 @@ org.projectlombok lombok - 1.16.18 provided org.mapstruct mapstruct-jdk8 - ${org.mapstruct.version} org.mapstruct mapstruct-processor - ${org.mapstruct.version} provided @@ -557,7 +547,6 @@ target/scm-it default 2.53.1 - 2.9.1 1.0 0.8.17 Tomcat From 7c8fee564079fabd7faa857edd5ad446f2ad8a9d Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 14:11:27 +0200 Subject: [PATCH 08/45] Move Mapper binding to GitServletModule --- .../scm/api/v2/resources/MapperModule.java | 18 ------------------ .../java/sonia/scm/web/GitServletModule.java | 9 ++++++--- 2 files changed, 6 insertions(+), 21 deletions(-) delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java deleted file mode 100644 index fc2f456d46..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ /dev/null @@ -1,18 +0,0 @@ -package sonia.scm.api.v2.resources; - -import com.google.inject.AbstractModule; -import com.google.inject.servlet.ServletScopes; -import org.mapstruct.factory.Mappers; -import sonia.scm.plugin.Extension; - -@Extension -public class MapperModule extends AbstractModule { - @Override - protected void configure() { - - bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass()); - bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass()); - - bind(UriInfoStore.class).in(ServletScopes.REQUEST); - } -} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java index 3d3442ce2a..bdad103c15 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java @@ -36,11 +36,11 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.servlet.ServletModule; - import org.eclipse.jgit.transport.ScmTransportProtocol; - +import org.mapstruct.factory.Mappers; +import sonia.scm.api.v2.resources.GitConfigDtoToGitConfigMapper; +import sonia.scm.api.v2.resources.GitConfigToGitConfigDtoMapper; import sonia.scm.plugin.Extension; - import sonia.scm.web.lfs.LfsBlobStoreFactory; /** @@ -73,6 +73,9 @@ public class GitServletModule extends ServletModule bind(LfsBlobStoreFactory.class); + bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass()); + bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass()); + // serlvelts and filters serve(PATTERN_GIT).with(ScmGitServlet.class); } From 4910744e1d95333e4a0396db5d7aac7477062ca5 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 14:13:10 +0200 Subject: [PATCH 09/45] Make LinkBuilder accessible for plugins --- .../src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java index a659d49004..75d69fcf85 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java @@ -23,7 +23,8 @@ import java.util.Arrays; * .create(); * */ -class LinkBuilder { +@SuppressWarnings("WeakerAccess") // Non-public will result in IllegalAccessError for plugins +public class LinkBuilder { private final UriInfo uriInfo; private final Class[] classes; private final ImmutableList calls; From 48e940e496d828670e87c49a87ef1bebef903568 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 14:13:45 +0200 Subject: [PATCH 10/45] Changes URL for new Git config to /config/git. Fits well with "global" config available at /config --- .../main/java/sonia/scm/api/v2/resources/GitConfigResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index 0e17529ae7..1db28dfb5d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -18,7 +18,7 @@ import javax.ws.rs.core.UriInfo; @Path(GitConfigResource.GIT_CONFIG_PATH_V2) public class GitConfigResource { - static final String GIT_CONFIG_PATH_V2 = "v2/config/repositories/git"; + static final String GIT_CONFIG_PATH_V2 = "v2/config/git"; private final GitConfigDtoToGitConfigMapper dtoToConfigMapper; private final GitConfigToGitConfigDtoMapper configToDtoMapper; private final GitRepositoryHandler repositoryHandler; From 0592e70bc6412a45201bf732c4bf3dca3b77caa6 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 14:42:41 +0200 Subject: [PATCH 11/45] Make also LinkBuilder's methods and inner classes accessible for plugins --- .../sonia/scm/api/v2/resources/LinkBuilder.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java index 75d69fcf85..6f6831b058 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java @@ -29,7 +29,7 @@ public class LinkBuilder { private final Class[] classes; private final ImmutableList calls; - LinkBuilder(UriInfo uriInfo, Class... classes) { + public LinkBuilder(UriInfo uriInfo, Class... classes) { this(uriInfo, classes, ImmutableList.of()); } @@ -39,25 +39,24 @@ public class LinkBuilder { this.calls = calls; } - Parameters method(String method) { + public Parameters method(String method) { if (calls.size() >= classes.length) { throw new IllegalStateException("no more classes for methods"); } return new Parameters(method); } - URI create() { + public URI create() { if (calls.size() < classes.length) { throw new IllegalStateException("not enough methods for all classes"); } URI baseUri = uriInfo.getBaseUri(); URI relativeUri = createRelativeUri(); - URI absoluteUri = baseUri.resolve(relativeUri); - return absoluteUri; + return baseUri.resolve(relativeUri); } - String href() { + public String href() { return create().toString(); } @@ -88,7 +87,7 @@ public class LinkBuilder { return UriBuilder.fromResource(classes[0]); } - class Parameters { + public class Parameters { private final String method; @@ -96,7 +95,7 @@ public class LinkBuilder { this.method = method; } - LinkBuilder parameters(String... parameters) { + public LinkBuilder parameters(String... parameters) { return LinkBuilder.this.add(method, parameters); } } From 59b60d104ca3e8f6014363239136bd933aefc610 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 14:44:42 +0200 Subject: [PATCH 12/45] GitConfigDto: Use camel case, get rid of base class. There's no parent module for a shared SimpleRepositoryConfigDto for git, hg and svn. scm-plugins and core are too generic. The redundancy (one property) does not hurt too much right now. --- .../scm/api/v2/resources/GitConfigDto.java | 7 ++++--- .../v2/resources/SimpleRepositoryConfigDto.java | 17 ----------------- 2 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java index 2bd2cf755c..ac1db11642 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java @@ -6,15 +6,16 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.xml.bind.annotation.XmlElement; +import java.io.File; @NoArgsConstructor @Getter @Setter -public class GitConfigDto extends SimpleRepositoryConfigDto { +public class GitConfigDto extends HalRepresentation { - @XmlElement(name = "gc-expression") private String gcExpression; + private File repositoryDirectory; + private boolean disabled = false; @Override protected HalRepresentation add(Links links) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java deleted file mode 100644 index 45cb0bd199..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.HalRepresentation; -import lombok.Getter; -import lombok.Setter; - -import javax.xml.bind.annotation.XmlElement; -import java.io.File; - -@Getter -@Setter -public abstract class SimpleRepositoryConfigDto extends HalRepresentation { - - private boolean disabled = false; - @XmlElement(name = "repository-directory") - private File repositoryDirectory; -} From fc77ae3a4791a28e90b13d6562c7c784ffff6d8f Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 15:15:07 +0200 Subject: [PATCH 13/45] Adapts tests to new GitResource URL --- .../sonia/scm/api/v2/resources/GitConfigResourceTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 3665be0648..46e47bbf38 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -75,10 +75,12 @@ public class GitConfigResourceTest { assertTrue(response.getContentAsString().contains("\"disabled\":false")); //assertTrue(response.getContentAsString().contains("\"repository-directory\":\"repository/directory\"")); //assertTrue(response.getContentAsString().contains("\"gc-expression\":\"valid Git GC Cron Expression\"")); - assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config/repositories/git")); - assertTrue(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/repositories/git")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config/git")); + assertTrue(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/git")); } + // TODO negative tests + private GitConfig createConfiguration() { GitConfig config = new GitConfig(); //config.setGcExpression("valid Git GC Cron Expression"); From 86af96bd83f749850fbf80e379e359a5ba733164 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 15:34:17 +0200 Subject: [PATCH 14/45] GitConfigResourceTest: Realizes missing asserts --- .../v2/resources/GitConfigResourceTest.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 46e47bbf38..7535e43db5 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import org.jboss.resteasy.core.Dispatcher; @@ -16,10 +18,10 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; -import sonia.scm.store.ConfigurationStoreFactory; import javax.servlet.http.HttpServletResponse; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -52,38 +54,39 @@ public class GitConfigResourceTest { private GitConfigToGitConfigDtoMapperImpl configToDtoMapper; @Mock - ConfigurationStoreFactory storeFactory; - - @InjectMocks private GitRepositoryHandler repositoryHandler; @Before public void prepareEnvironment() { GitConfig gitConfig = createConfiguration(); - repositoryHandler.setConfig(gitConfig); + when(repositoryHandler.getConfig()).thenReturn(gitConfig); GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); dispatcher.getRegistry().addSingletonResource(gitConfigResource); when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); } @Test - public void shouldGetGitConfig() throws URISyntaxException { + public void shouldGetGitConfig() throws URISyntaxException, IOException { MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); - assertTrue(response.getContentAsString().contains("\"disabled\":false")); - //assertTrue(response.getContentAsString().contains("\"repository-directory\":\"repository/directory\"")); - //assertTrue(response.getContentAsString().contains("\"gc-expression\":\"valid Git GC Cron Expression\"")); - assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config/git")); - assertTrue(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/git")); + + String responseString = response.getContentAsString(); + ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class); + + assertTrue(responseString.contains("\"disabled\":false")); + assertTrue(responseJson.get("repositoryDirectory").asText().endsWith("repository/directory")); + assertTrue(responseString.contains("\"gcExpression\":\"valid Git GC Cron Expression\"")); + assertTrue(responseString.contains("\"self\":{\"href\":\"/v2/config/git")); + assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/git")); } - // TODO negative tests + // TODO update & negative tests private GitConfig createConfiguration() { GitConfig config = new GitConfig(); - //config.setGcExpression("valid Git GC Cron Expression"); + config.setGcExpression("valid Git GC Cron Expression"); config.setDisabled(false); config.setRepositoryDirectory(new File("repository/directory")); return config; From aed70d354415d9f64e5e1c86a9f9c317b5a93edf Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 16:21:05 +0200 Subject: [PATCH 15/45] Git Plugin Config: Create fine-grained configuration permissions. No more hard-coded isAdmin() checks. Also adds more tests. --- .../api/v2/resources/GitConfigResource.java | 49 +++++++------- .../GitConfigToGitConfigDtoMapper.java | 6 +- .../GitConfigDtoToGitConfigMapperTest.java | 3 +- .../v2/resources/GitConfigResourceTest.java | 67 +++++++++++++++++-- .../GitConfigToGitConfigDtoMapperTest.java | 14 +++- .../sonia/scm/configuration/shiro.ini | 9 +++ .../resources/sonia/scm/repository/shiro.ini | 11 --- .../scm/api/v2/resources/ConfigResource.java | 2 +- .../api/v2/resources/ConfigResourceTest.java | 3 +- .../sonia/scm/configuration/shiro.ini | 6 +- 10 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini delete mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index 1db28dfb5d..ddd6bf3823 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -3,18 +3,24 @@ package sonia.scm.api.v2.resources; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import org.apache.shiro.SecurityUtils; +import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; -import sonia.scm.security.Role; import sonia.scm.web.GitVndMediaType; import javax.inject.Inject; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +/** + * RESTful Web Service Resource to manage the configuration of the git plugin. + */ @Path(GitConfigResource.GIT_CONFIG_PATH_V2) public class GitConfigResource { @@ -44,22 +50,17 @@ public class GitConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) public Response get() { - Response response; - if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { - GitConfig config = repositoryHandler.getConfig(); + GitConfig config = repositoryHandler.getConfig(); - if (config == null) { - config = new GitConfig(); - repositoryHandler.setConfig(config); - } - - response = Response.ok(configToDtoMapper.map(config)).build(); - } else { - response = Response.status(Response.Status.FORBIDDEN).build(); + if (config == null) { + config = new GitConfig(); + repositoryHandler.setConfig(config); } - return response; + ConfigurationPermissions.read(config).check(); + + return Response.ok(configToDtoMapper.map(config)).build(); } /** @@ -71,23 +72,21 @@ public class GitConfigResource { @Path("") @Consumes(GitVndMediaType.GIT_CONFIG) @StatusCodes({ - @ResponseCode(code = 201, condition = "update success"), + @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) public Response update(@Context UriInfo uriInfo, GitConfigDto configDto) { - Response response; - if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { - repositoryHandler.setConfig(dtoToConfigMapper.map(configDto)); - repositoryHandler.storeConfig(); - response = Response.created(uriInfo.getRequestUri()).build(); - } else { - response = Response.status(Response.Status.FORBIDDEN).build(); - } + GitConfig config = dtoToConfigMapper.map(configDto); - return response; + ConfigurationPermissions.write(config).check(); + + repositoryHandler.setConfig(config); + repositoryHandler.storeConfig(); + + return Response.noContent().build(); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java index ceab24d6f7..b929d6522b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java @@ -1,12 +1,11 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; -import org.apache.shiro.SecurityUtils; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.GitConfig; -import sonia.scm.security.Role; import javax.inject.Inject; @@ -26,8 +25,7 @@ public abstract class GitConfigToGitConfigDtoMapper { @AfterMapping void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) { Links.Builder linksBuilder = linkingTo().self(self()); - // TODO: ConfigPermissions? - if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + if (ConfigurationPermissions.write(config).isPermitted()) { linksBuilder.single(link("update", update())); } target.add(linksBuilder.build()); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java index 3b09dff51f..968b6b7f6f 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java @@ -9,6 +9,7 @@ import sonia.scm.repository.GitConfig; import java.io.File; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; @RunWith(MockitoJUnitRunner.class) public class GitConfigDtoToGitConfigMapperTest { @@ -22,7 +23,7 @@ public class GitConfigDtoToGitConfigMapperTest { GitConfig config = mapper.map(dto); assertEquals("express", config.getGcExpression()); assertEquals("repository/directory", config.getRepositoryDirectory().getPath()); - assertEquals(false, config.isDisabled()); + assertFalse(config.isDisabled()); } private GitConfigDto createDefaultDto() { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 7535e43db5..2389f4abd7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -11,6 +11,7 @@ import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -18,6 +19,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.web.GitVndMediaType; import javax.servlet.http.HttpServletResponse; import java.io.File; @@ -27,12 +29,12 @@ import java.net.URISyntaxException; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.when; @SubjectAware( - username = "trillian", - password = "secret", - configuration = "classpath:sonia/scm/repository/shiro.ini" + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" ) @RunWith(MockitoJUnitRunner.class) public class GitConfigResourceTest { @@ -40,6 +42,9 @@ public class GitConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); private final URI baseUri = URI.create("/"); @@ -66,10 +71,10 @@ public class GitConfigResourceTest { } @Test + @SubjectAware(username = "readWrite") public void shouldGetGitConfig() throws URISyntaxException, IOException { - MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2); - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); + MockHttpResponse response = get(); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); String responseString = response.getContentAsString(); @@ -82,7 +87,55 @@ public class GitConfigResourceTest { assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/git")); } - // TODO update & negative tests + @Test + @SubjectAware(username = "readOnly") + public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException { + MockHttpResponse response = get(); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + assertFalse(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/git")); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + thrown.expectMessage("Subject does not have permission [configuration:read:git]"); + + get(); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldUpdateConfig() throws URISyntaxException { + MockHttpResponse response = put(); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + thrown.expectMessage("Subject does not have permission [configuration:write:git]"); + + put(); + } + + private MockHttpResponse get() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private MockHttpResponse put() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2) + .contentType(GitVndMediaType.GIT_CONFIG) + .content("{\"disabled\":true}".getBytes()); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } private GitConfig createConfiguration() { GitConfig config = new GitConfig(); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java index 59f7cc4799..51fded4839 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java @@ -13,7 +13,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; -import sonia.scm.security.Role; import java.io.File; import java.net.URI; @@ -56,7 +55,7 @@ public class GitConfigToGitConfigDtoMapperTest { public void shouldMapFields() { GitConfig config = createConfiguration(); - when(subject.hasRole(Role.ADMIN)).thenReturn(true); + when(subject.isPermitted("configuration:write:git")).thenReturn(true); GitConfigDto dto = mapper.map(config); assertEquals("express", dto.getGcExpression()); @@ -66,6 +65,17 @@ public class GitConfigToGitConfigDtoMapperTest { assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); } + @Test + public void shouldMapFieldsWithoutUpdate() { + GitConfig config = createConfiguration(); + + when(subject.isPermitted("configuration:write:git")).thenReturn(false); + GitConfigDto dto = mapper.map(config); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertFalse(dto.getLinks().hasLink("update")); + } + private GitConfig createConfiguration() { GitConfig config = new GitConfig(); config.setDisabled(false); diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini new file mode 100644 index 0000000000..36226edd7d --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini @@ -0,0 +1,9 @@ +[users] +readOnly = secret, reader +writeOnly = secret, writer +readWrite = secret, readerWriter + +[roles] +reader = configuration:read:git +writer = configuration:write:git +readerWriter = configuration:*:git diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini deleted file mode 100644 index 5073bf398d..0000000000 --- a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/shiro.ini +++ /dev/null @@ -1,11 +0,0 @@ -[users] -trillian = secret, admin -dent = secret, creator, heartOfGold, puzzle42 -unpriv = secret -crato = secret, creator - -[roles] -admin = * -creator = repository:create -heartOfGold = "repository:read,modify,delete:hof" -puzzle42 = "repository:read,write:p42" diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index bf3da19416..1415605770 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -64,7 +64,7 @@ public class ConfigResource { @Path("") @Consumes(VndMediaType.CONFIG) @StatusCodes({ - @ResponseCode(code = 201, condition = "update success"), + @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the global config"), @ResponseCode(code = 500, condition = "internal server error") diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 5365e9fbde..2a8a44b4d1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -96,8 +96,7 @@ public class ConfigResourceTest { request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); response = new MockHttpResponse(); - dispatcher.invoke(request, response); - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + dispatcher.invoke(request, response); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\"")); assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); diff --git a/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini index 8647142b19..fc2eac14f1 100644 --- a/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini +++ b/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini @@ -4,6 +4,6 @@ writeOnly = secret, writer readWrite = secret, readerWriter [roles] -reader = configuration:read -writer = configuration:write -readerWriter = configuration:* +reader = configuration:read:global +writer = configuration:write:global +readerWriter = configuration:*:global From 94582554ca704e53f456d3aada83d603c08b37b8 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 1 Aug 2018 16:29:37 +0200 Subject: [PATCH 16/45] Git Plugin Config: Adds test for empty config --- .../scm/api/v2/resources/GitConfigResourceTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 2389f4abd7..feed89aa8f 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -87,6 +87,17 @@ public class GitConfigResourceTest { assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/git")); } + @Test + @SubjectAware(username = "readWrite") + public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, IOException { + when(repositoryHandler.getConfig()).thenReturn(null); + + MockHttpResponse response = get(); + String responseString = response.getContentAsString(); + + assertTrue(responseString.contains("\"disabled\":false")); + } + @Test @SubjectAware(username = "readOnly") public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException { From 7fd944357d62d14557e359231bb29dbb06a3790a Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 10:45:59 +0200 Subject: [PATCH 17/45] Config DTO: Adds namespace strategy --- .../src/main/java/sonia/scm/api/v2/resources/ConfigDto.java | 1 + .../scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java | 2 ++ .../api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java | 2 ++ .../api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java | 2 ++ 4 files changed, 7 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java index 13d9ff5351..4c9620564b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java @@ -33,6 +33,7 @@ public class ConfigDto extends HalRepresentation { private String pluginUrl; private long loginAttemptLimitTimeout; private boolean enabledXsrfProtection; + private String defaultNamespaceStrategy; @Override @SuppressWarnings("squid:S1185") // We want to have this method available in this package diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java index 59e56feb11..ee0d32b0f8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java @@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.config.ScmConfiguration; @@ -20,6 +21,7 @@ public abstract class ScmConfigurationToConfigDtoMapper { @Inject private ResourceLinks resourceLinks; + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract ConfigDto map(ScmConfiguration config); @AfterMapping diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java index ad1e71368e..525d5814e1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java @@ -51,6 +51,7 @@ public class ConfigDtoToScmConfigurationMapperTest { assertEquals("https://plug.ins" , config.getPluginUrl()); assertEquals(40 , config.getLoginAttemptLimitTimeout()); assertTrue(config.isEnabledXsrfProtection()); + assertEquals("username", config.getDefaultNamespaceStrategy()); } private ConfigDto createDefaultDto() { @@ -75,6 +76,7 @@ public class ConfigDtoToScmConfigurationMapperTest { configDto.setPluginUrl("https://plug.ins"); configDto.setLoginAttemptLimitTimeout(40); configDto.setEnabledXsrfProtection(true); + configDto.setDefaultNamespaceStrategy("username"); return configDto; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java index ff20516e5a..cdbd9ae344 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java @@ -81,6 +81,7 @@ public class ScmConfigurationToConfigDtoMapperTest { assertEquals("pluginurl" , dto.getPluginUrl()); assertEquals(2 , dto.getLoginAttemptLimitTimeout()); assertTrue(dto.isEnabledXsrfProtection()); + assertEquals("username", dto.getDefaultNamespaceStrategy()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); @@ -120,6 +121,7 @@ public class ScmConfigurationToConfigDtoMapperTest { config.setPluginUrl("pluginurl"); config.setLoginAttemptLimitTimeout(2); config.setEnabledXsrfProtection(true); + config.setDefaultNamespaceStrategy("username"); return config; } From 79f807e8092ada75504938ec8bb57f698ce031b6 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 10:53:13 +0200 Subject: [PATCH 18/45] Git Plugin Config: Gets rid of maven warning. Mapstruct "attributes" not mapped -> Field of HAL base class should be ignored. --- .../src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java | 3 ++- .../java/sonia/scm/api/v2/resources/GitConfigResource.java | 3 ++- .../scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java index ac1db11642..8ecea58a19 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java @@ -13,10 +13,11 @@ import java.io.File; @Setter public class GitConfigDto extends HalRepresentation { - private String gcExpression; private File repositoryDirectory; private boolean disabled = false; + private String gcExpression; + @Override protected HalRepresentation add(Links links) { return super.add(links); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index ddd6bf3823..8b8be14397 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -30,7 +30,8 @@ public class GitConfigResource { private final GitRepositoryHandler repositoryHandler; @Inject - public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper, GitRepositoryHandler repositoryHandler) { + public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper, + GitRepositoryHandler repositoryHandler) { this.dtoToConfigMapper = dtoToConfigMapper; this.configToDtoMapper = configToDtoMapper; this.repositoryHandler = repositoryHandler; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java index b929d6522b..a4eb8e29a7 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java @@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.GitConfig; @@ -20,6 +21,7 @@ public abstract class GitConfigToGitConfigDtoMapper { @Inject private UriInfoStore uriInfoStore; + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract GitConfigDto map(GitConfig config); @AfterMapping From eee325e185f08938f10eec12289af4e28899a545 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 10:59:34 +0200 Subject: [PATCH 19/45] HgPlugin Config: Adds v2 endpoint --- scm-plugins/scm-hg-plugin/pom.xml | 11 -- .../scm/api/v2/resources/HgConfigDto.java | 30 ++++ .../HgConfigDtoToHgConfigMapper.java | 11 ++ .../api/v2/resources/HgConfigResource.java | 100 +++++++++++ .../HgConfigToHgConfigDtoMapper.java | 45 +++++ .../sonia/scm/repository/HgVndMediaType.java | 7 + .../java/sonia/scm/web/HgServletModule.java | 7 +- .../sonia/scm/configuration/shiro.ini | 9 + .../HgConfigDtoToHgConfigMapperTest.java | 49 ++++++ .../v2/resources/HgConfigResourceTest.java | 158 ++++++++++++++++++ .../HgConfigToHgConfigDtoMapperTest.java | 102 +++++++++++ 11 files changed, 517 insertions(+), 12 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapper.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/configuration/shiro.ini create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 67b6b3e684..d3381e3d22 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -9,9 +9,7 @@ 2.0.0-SNAPSHOT - sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT scm-hg-plugin smp https://bitbucket.org/sdorra/scm-manager @@ -30,15 +28,6 @@ - - - - - sonia.scm - scm-test - 2.0.0-SNAPSHOT - test - diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java new file mode 100644 index 0000000000..889c156f63 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java @@ -0,0 +1,30 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.File; + +@NoArgsConstructor +@Getter +@Setter +public class HgConfigDto extends HalRepresentation { + + private boolean disabled; + private File repositoryDirectory; + + private String encoding; + private String hgBinary; + private String pythonBinary; + private String pythonPath; + private boolean useOptimizedBytecode; + private boolean showRevisionInId; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapper.java new file mode 100644 index 0000000000..af3879013e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapper.java @@ -0,0 +1,11 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.repository.HgConfig; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class HgConfigDtoToHgConfigMapper { + public abstract HgConfig map(HgConfigDto dto); +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java new file mode 100644 index 0000000000..b667d48313 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -0,0 +1,100 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.repository.HgVndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * RESTful Web Service Resource to manage the configuration of the hg plugin. + */ +@Path(HgConfigResource.GIT_CONFIG_PATH_V2) +public class HgConfigResource { + + static final String GIT_CONFIG_PATH_V2 = "v2/config/hg"; + private final HgConfigDtoToHgConfigMapper dtoToConfigMapper; + private final HgConfigToHgConfigDtoMapper configToDtoMapper; + private final HgRepositoryHandler repositoryHandler; + + @Inject + public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper, + HgRepositoryHandler repositoryHandler) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.repositoryHandler = repositoryHandler; + } + + /** + * Returns the hg config. + */ + @GET + @Path("") + @Produces(HgVndMediaType.HG_CONFIG) + @TypeHint(HgConfigDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + + HgConfig config = repositoryHandler.getConfig(); + + if (config == null) { + config = new HgConfig(); + repositoryHandler.setConfig(config); + } + + ConfigurationPermissions.read(config).check(); + + return Response.ok(configToDtoMapper.map(config)).build(); + } + + /** + * Modifies the hg config. + * + * @param configDto new git configuration as DTO + */ + @PUT + @Path("") + @Consumes(HgVndMediaType.HG_CONFIG) + @StatusCodes({ + @ResponseCode(code = 204, condition = "update success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(@Context UriInfo uriInfo, HgConfigDto configDto) { + + HgConfig config = dtoToConfigMapper.map(configDto); + + ConfigurationPermissions.write(config).check(); + + repositoryHandler.setConfig(config); + repositoryHandler.storeConfig(); + + return Response.noContent().build(); + } + + // TODO + //* `auto-configuration` + // * `packages` + // * `packages/{pkgId}` + // * `installations/hg` + // * `installations/python +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java new file mode 100644 index 0000000000..464f32a5a4 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java @@ -0,0 +1,45 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.repository.HgConfig; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class HgConfigToHgConfigDtoMapper { + + @Inject + private UriInfoStore uriInfoStore; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract HgConfigDto map(HgConfig config); + + @AfterMapping + void appendLinks(HgConfig config, @MappingTarget HgConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(self()); + if (ConfigurationPermissions.write(config).isPermitted()) { + linksBuilder.single(link("update", update())); + } + target.add(linksBuilder.build()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); + return linkBuilder.method("get").parameters().href(); + } + + private String update() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); + return linkBuilder.method("update").parameters().href(); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java new file mode 100644 index 0000000000..003b2744af --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java @@ -0,0 +1,7 @@ +package sonia.scm.repository; + +import sonia.scm.web.VndMediaType; + +public class HgVndMediaType { + public static final String HG_CONFIG = VndMediaType.PREFIX + "hgConfig" + VndMediaType.SUFFIX; +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index 71d6f9f419..b9cf1e150d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -36,7 +36,9 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.servlet.ServletModule; - +import org.mapstruct.factory.Mappers; +import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper; +import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper; import sonia.scm.installer.HgPackageReader; import sonia.scm.plugin.Extension; import sonia.scm.repository.HgContext; @@ -70,6 +72,9 @@ public class HgServletModule extends ServletModule bind(HgHookManager.class); bind(HgPackageReader.class); + bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass()); + bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass()); + // bind servlets serve(MAPPING_HOOK).with(HgHookCallbackServlet.class); diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/configuration/shiro.ini new file mode 100644 index 0000000000..fc08bb83ac --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/configuration/shiro.ini @@ -0,0 +1,9 @@ +[users] +readOnly = secret, reader +writeOnly = secret, writer +readWrite = secret, readerWriter + +[roles] +reader = configuration:read:hg +writer = configuration:write:hg +readerWriter = configuration:*:hg diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java new file mode 100644 index 0000000000..b95056892e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java @@ -0,0 +1,49 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.HgConfig; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(MockitoJUnitRunner.class) +public class HgConfigDtoToHgConfigMapperTest { + + @InjectMocks + private HgConfigDtoToHgConfigMapperImpl mapper; + + @Test + public void shouldMapFields() { + HgConfigDto dto = createDefaultDto(); + HgConfig config = mapper.map(dto); + + assertTrue(config.isDisabled()); + assertEquals("repository/directory", config.getRepositoryDirectory().getPath()); + + assertEquals("ABC", config.getEncoding()); + assertEquals("/etc/hg", config.getHgBinary()); + assertEquals("/py", config.getPythonBinary()); + assertEquals("/etc/", config.getPythonPath()); + assertTrue(config.isShowRevisionInId()); + assertTrue(config.isUseOptimizedBytecode()); + } + + private HgConfigDto createDefaultDto() { + HgConfigDto configDto = new HgConfigDto(); + configDto.setDisabled(true); + configDto.setRepositoryDirectory(new File("repository/directory")); + configDto.setEncoding("ABC"); + configDto.setHgBinary("/etc/hg"); + configDto.setPythonBinary("/py"); + configDto.setPythonPath("/etc/"); + configDto.setShowRevisionInId(true); + configDto.setUseOptimizedBytecode(true); + + return configDto; + } +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java new file mode 100644 index 0000000000..a0e15c9fee --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -0,0 +1,158 @@ +package sonia.scm.api.v2.resources; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.repository.HgVndMediaType; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.when; + +@SubjectAware( + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" +) +@RunWith(MockitoJUnitRunner.class) +public class HgConfigResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = URI.create("/"); + + @InjectMocks + private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private HgConfigToHgConfigDtoMapperImpl configToDtoMapper; + + @Mock + private HgRepositoryHandler repositoryHandler; + + @Before + public void prepareEnvironment() { + HgConfig gitConfig = createConfiguration(); + when(repositoryHandler.getConfig()).thenReturn(gitConfig); + HgConfigResource gitConfigResource = new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); + dispatcher.getRegistry().addSingletonResource(gitConfigResource); + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldGetHgConfig() throws URISyntaxException, IOException { + MockHttpResponse response = get(); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + String responseString = response.getContentAsString(); + ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class); + + assertTrue(responseString.contains("\"disabled\":false")); + assertTrue(responseJson.get("repositoryDirectory").asText().endsWith("repository/directory")); + assertTrue(responseString.contains("\"self\":{\"href\":\"/v2/config/hg")); + assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/hg")); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, IOException { + when(repositoryHandler.getConfig()).thenReturn(null); + + MockHttpResponse response = get(); + String responseString = response.getContentAsString(); + + assertTrue(responseString.contains("\"disabled\":false")); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException { + MockHttpResponse response = get(); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + assertFalse(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/hg")); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + + get(); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldUpdateConfig() throws URISyntaxException { + MockHttpResponse response = put(); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + + put(); + } + + private MockHttpResponse get() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.GIT_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private MockHttpResponse put() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.GIT_CONFIG_PATH_V2) + .contentType(HgVndMediaType.HG_CONFIG) + .content("{\"disabled\":true}".getBytes()); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private HgConfig createConfiguration() { + HgConfig config = new HgConfig(); + config.setDisabled(false); + config.setRepositoryDirectory(new File("repository/directory")); + return config; + } + +} + diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java new file mode 100644 index 0000000000..7033ce966a --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java @@ -0,0 +1,102 @@ +package sonia.scm.api.v2.resources; + +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.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.HgConfig; + +import java.io.File; +import java.net.URI; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class HgConfigToHgConfigDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private HgConfigToHgConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + expectedBaseUri = baseUri.resolve(HgConfigResource.GIT_CONFIG_PATH_V2); + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @After + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldMapFields() { + HgConfig config = createConfiguration(); + + when(subject.isPermitted("configuration:write:hg")).thenReturn(true); + HgConfigDto dto = mapper.map(config); + + assertTrue(dto.isDisabled()); + assertEquals("repository/directory", dto.getRepositoryDirectory().getPath()); + + assertEquals("ABC", dto.getEncoding()); + assertEquals("/etc/hg", dto.getHgBinary()); + assertEquals("/py", dto.getPythonBinary()); + assertEquals("/etc/", dto.getPythonPath()); + assertTrue(dto.isShowRevisionInId()); + assertTrue(dto.isUseOptimizedBytecode()); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + @Test + public void shouldMapFieldsWithoutUpdate() { + HgConfig config = createConfiguration(); + + when(subject.isPermitted("configuration:write:hg")).thenReturn(false); + HgConfigDto dto = mapper.map(config); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertFalse(dto.getLinks().hasLink("update")); + } + + private HgConfig createConfiguration() { + HgConfig config = new HgConfig(); + config.setDisabled(true); + config.setRepositoryDirectory(new File("repository/directory")); + + config.setEncoding("ABC"); + config.setHgBinary("/etc/hg"); + config.setPythonBinary("/py"); + config.setPythonPath("/etc/"); + config.setShowRevisionInId(true); + config.setUseOptimizedBytecode(true); + + return config; + } + +} From 31540a7ecc6d294dfb0119e3b2f681cd790c4f08 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 11:52:30 +0200 Subject: [PATCH 20/45] Makes REST API doc for config endpoints more precise --- .../main/java/sonia/scm/api/v2/resources/GitConfigResource.java | 2 +- .../main/java/sonia/scm/api/v2/resources/HgConfigResource.java | 2 +- .../main/java/sonia/scm/api/v2/resources/ConfigResource.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index 8b8be14397..265a80ef6e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -67,7 +67,7 @@ public class GitConfigResource { /** * Modifies the git config. * - * @param configDto new git configuration as DTO + * @param configDto new configuration object */ @PUT @Path("") diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index b667d48313..e35b380703 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -67,7 +67,7 @@ public class HgConfigResource { /** * Modifies the hg config. * - * @param configDto new git configuration as DTO + * @param configDto new configuration object */ @PUT @Path("") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index 1415605770..7bfa28f02b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -58,7 +58,7 @@ public class ConfigResource { /** * Modifies the global scm config. * - * @param configDto new global scm configuration as DTO + * @param configDto new configuration object */ @PUT @Path("") From f20865c6584dd8abbfe15010d8caa29b2fccf981 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 11:56:06 +0200 Subject: [PATCH 21/45] HgPlugin Config: Minor corrections --- .../java/sonia/scm/api/v2/resources/HgConfigResource.java | 6 +++--- .../sonia/scm/{repository => web}/HgVndMediaType.java | 2 +- .../sonia/scm/api/v2/resources/HgConfigResourceTest.java | 8 ++++---- .../api/v2/resources/HgConfigToHgConfigDtoMapperTest.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/{repository => web}/HgVndMediaType.java (84%) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index e35b380703..36838507ea 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -6,7 +6,7 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.HgVndMediaType; +import sonia.scm.web.HgVndMediaType; import javax.inject.Inject; import javax.ws.rs.Consumes; @@ -21,10 +21,10 @@ import javax.ws.rs.core.UriInfo; /** * RESTful Web Service Resource to manage the configuration of the hg plugin. */ -@Path(HgConfigResource.GIT_CONFIG_PATH_V2) +@Path(HgConfigResource.HG_CONFIG_PATH_V2) public class HgConfigResource { - static final String GIT_CONFIG_PATH_V2 = "v2/config/hg"; + static final String HG_CONFIG_PATH_V2 = "v2/config/hg"; private final HgConfigDtoToHgConfigMapper dtoToConfigMapper; private final HgConfigToHgConfigDtoMapper configToDtoMapper; private final HgRepositoryHandler repositoryHandler; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java similarity index 84% rename from scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java rename to scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java index 003b2744af..b3c5e1f00d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgVndMediaType.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java @@ -1,4 +1,4 @@ -package sonia.scm.repository; +package sonia.scm.web; import sonia.scm.web.VndMediaType; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index a0e15c9fee..f260a9f6d1 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -19,7 +19,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.HgVndMediaType; +import sonia.scm.web.HgVndMediaType; import javax.servlet.http.HttpServletResponse; import java.io.File; @@ -124,21 +124,21 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException { thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); put(); } private MockHttpResponse get() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.GIT_CONFIG_PATH_V2); + MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.HG_CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); return response; } private MockHttpResponse put() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.GIT_CONFIG_PATH_V2) + MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.HG_CONFIG_PATH_V2) .contentType(HgVndMediaType.HG_CONFIG) .content("{\"disabled\":true}".getBytes()); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java index 7033ce966a..4c61874870 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java @@ -42,7 +42,7 @@ public class HgConfigToHgConfigDtoMapperTest { @Before public void init() { when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); - expectedBaseUri = baseUri.resolve(HgConfigResource.GIT_CONFIG_PATH_V2); + expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2); subjectThreadState.bind(); ThreadContext.bind(subject); } From 8b16a0251a197d42d41e26e8a32875952ceb8e4d Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 13:02:43 +0200 Subject: [PATCH 22/45] VndMedia Type classes: Adds private constructor --- .../src/main/java/sonia/scm/web/GitVndMediaType.java | 3 +++ .../src/main/java/sonia/scm/web/HgVndMediaType.java | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java index f099faf098..8c81c6eefa 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java @@ -2,4 +2,7 @@ package sonia.scm.web; public class GitVndMediaType { public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX; + + private GitVndMediaType() { + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java index b3c5e1f00d..6f31090d58 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java @@ -1,7 +1,9 @@ package sonia.scm.web; -import sonia.scm.web.VndMediaType; public class HgVndMediaType { public static final String HG_CONFIG = VndMediaType.PREFIX + "hgConfig" + VndMediaType.SUFFIX; + + private HgVndMediaType() { + } } From 54354590ac20253ec381fb8a79a910f1c08fa61b Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 13:04:06 +0200 Subject: [PATCH 23/45] SvnPlugin Config: Adds v2 endpoint --- scm-plugins/scm-svn-plugin/pom.xml | 18 -- .../scm/api/v2/resources/SvnConfigDto.java | 27 +++ .../SvnConfigDtoToSvnConfigMapper.java | 11 ++ .../api/v2/resources/SvnConfigResource.java | 94 +++++++++++ .../SvnConfigToSvnConfigDtoMapper.java | 45 +++++ .../java/sonia/scm/web/SvnServletModule.java | 12 +- .../java/sonia/scm/web/SvnVndMediaType.java | 8 + .../SvnConfigDtoToSvnConfigMapperTest.java | 42 +++++ .../v2/resources/SvnConfigResourceTest.java | 158 ++++++++++++++++++ .../SvnConfigToSvnConfigDtoMapperTest.java | 95 +++++++++++ .../sonia/scm/configuration/shiro.ini | 9 + 11 files changed, 497 insertions(+), 22 deletions(-) create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapper.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnVndMediaType.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 9d37319394..701569e620 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -9,9 +9,7 @@ 2.0.0-SNAPSHOT - sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT scm-svn-plugin smp https://bitbucket.org/sdorra/scm-manager @@ -19,13 +17,6 @@ - - javax.servlet - javax.servlet-api - ${servlet.version} - provided - - sonia.svnkit svnkit @@ -44,15 +35,6 @@ ${svnkit.version} - - - - sonia.scm - scm-test - 2.0.0-SNAPSHOT - test - - diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java new file mode 100644 index 0000000000..d0a36f1a85 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java @@ -0,0 +1,27 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import sonia.scm.repository.Compatibility; + +import java.io.File; + +@NoArgsConstructor +@Getter +@Setter +public class SvnConfigDto extends HalRepresentation { + + private boolean disabled; + private File repositoryDirectory; + + private boolean enabledGZip; + private Compatibility compatibility; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapper.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapper.java new file mode 100644 index 0000000000..f996c49248 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapper.java @@ -0,0 +1,11 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.repository.SvnConfig; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class SvnConfigDtoToSvnConfigMapper { + public abstract SvnConfig map(SvnConfigDto dto); +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java new file mode 100644 index 0000000000..c8050f41d3 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java @@ -0,0 +1,94 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.repository.SvnConfig; +import sonia.scm.repository.SvnRepositoryHandler; +import sonia.scm.web.SvnVndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * RESTful Web Service Resource to manage the configuration of the svn plugin. + */ +@Path(SvnConfigResource.SVN_CONFIG_PATH_V2) +public class SvnConfigResource { + + static final String SVN_CONFIG_PATH_V2 = "v2/config/svn"; + private final SvnConfigDtoToSvnConfigMapper dtoToConfigMapper; + private final SvnConfigToSvnConfigDtoMapper configToDtoMapper; + private final SvnRepositoryHandler repositoryHandler; + + @Inject + public SvnConfigResource(SvnConfigDtoToSvnConfigMapper dtoToConfigMapper, SvnConfigToSvnConfigDtoMapper configToDtoMapper, + SvnRepositoryHandler repositoryHandler) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.repositoryHandler = repositoryHandler; + } + + /** + * Returns the svn config. + */ + @GET + @Path("") + @Produces(SvnVndMediaType.SVN_CONFIG) + @TypeHint(SvnConfigDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + + SvnConfig config = repositoryHandler.getConfig(); + + if (config == null) { + config = new SvnConfig(); + repositoryHandler.setConfig(config); + } + + ConfigurationPermissions.read(config).check(); + + return Response.ok(configToDtoMapper.map(config)).build(); + } + + /** + * Modifies the svn config. + * + * @param configDto new configuration object + */ + @PUT + @Path("") + @Consumes(SvnVndMediaType.SVN_CONFIG) + @StatusCodes({ + @ResponseCode(code = 204, condition = "update success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(@Context UriInfo uriInfo, SvnConfigDto configDto) { + + SvnConfig config = dtoToConfigMapper.map(configDto); + + ConfigurationPermissions.write(config).check(); + + repositoryHandler.setConfig(config); + repositoryHandler.storeConfig(); + + return Response.noContent().build(); + } + +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java new file mode 100644 index 0000000000..df974c3f3d --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java @@ -0,0 +1,45 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.repository.SvnConfig; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class SvnConfigToSvnConfigDtoMapper { + + @Inject + private UriInfoStore uriInfoStore; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract SvnConfigDto map(SvnConfig config); + + @AfterMapping + void appendLinks(SvnConfig config, @MappingTarget SvnConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(self()); + if (ConfigurationPermissions.write(config).isPermitted()) { + linksBuilder.single(link("update", update())); + } + target.add(linksBuilder.build()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), SvnConfigResource.class); + return linkBuilder.method("get").parameters().href(); + } + + private String update() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), SvnConfigResource.class); + return linkBuilder.method("update").parameters().href(); + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java index 813f7fb0e6..9b5c8ae556 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java @@ -36,15 +36,16 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.servlet.ServletModule; - +import org.mapstruct.factory.Mappers; +import sonia.scm.api.v2.resources.SvnConfigDtoToSvnConfigMapper; +import sonia.scm.api.v2.resources.SvnConfigToSvnConfigDtoMapper; import sonia.scm.plugin.Extension; -import sonia.scm.web.filter.AuthenticationFilter; - -//~--- JDK imports ------------------------------------------------------------ import java.util.HashMap; import java.util.Map; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -72,6 +73,9 @@ public class SvnServletModule extends ServletModule filter(PATTERN_SVN).through(SvnBasicAuthenticationFilter.class); filter(PATTERN_SVN).through(SvnPermissionFilter.class); + bind(SvnConfigDtoToSvnConfigMapper.class).to(Mappers.getMapper(SvnConfigDtoToSvnConfigMapper.class).getClass()); + bind(SvnConfigToSvnConfigDtoMapper.class).to(Mappers.getMapper(SvnConfigToSvnConfigDtoMapper.class).getClass()); + Map parameters = new HashMap(); parameters.put(PARAMETER_SVN_PARENTPATH, diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnVndMediaType.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnVndMediaType.java new file mode 100644 index 0000000000..1e294df1fe --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnVndMediaType.java @@ -0,0 +1,8 @@ +package sonia.scm.web; + +public class SvnVndMediaType { + public static final String SVN_CONFIG = VndMediaType.PREFIX + "svnConfig" + VndMediaType.SUFFIX; + + private SvnVndMediaType() { + } +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java new file mode 100644 index 0000000000..b111d0229f --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java @@ -0,0 +1,42 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.Compatibility; +import sonia.scm.repository.SvnConfig; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(MockitoJUnitRunner.class) +public class SvnConfigDtoToSvnConfigMapperTest { + + @InjectMocks + private SvnConfigDtoToSvnConfigMapperImpl mapper; + + @Test + public void shouldMapFields() { + SvnConfigDto dto = createDefaultDto(); + SvnConfig config = mapper.map(dto); + + assertTrue(config.isDisabled()); + assertEquals("repository/directory", config.getRepositoryDirectory().getPath()); + + assertEquals(Compatibility.PRE15, config.getCompatibility()); + assertTrue(config.isEnabledGZip()); + } + + private SvnConfigDto createDefaultDto() { + SvnConfigDto configDto = new SvnConfigDto(); + configDto.setDisabled(true); + configDto.setRepositoryDirectory(new File("repository/directory")); + configDto.setCompatibility(Compatibility.PRE15); + configDto.setEnabledGZip(true); + + return configDto; + } +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java new file mode 100644 index 0000000000..6c0601f977 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java @@ -0,0 +1,158 @@ +package sonia.scm.api.v2.resources; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.SvnConfig; +import sonia.scm.repository.SvnRepositoryHandler; +import sonia.scm.web.SvnVndMediaType; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.when; + +@SubjectAware( + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" +) +@RunWith(MockitoJUnitRunner.class) +public class SvnConfigResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = URI.create("/"); + + @InjectMocks + private SvnConfigDtoToSvnConfigMapperImpl dtoToConfigMapper; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private SvnConfigToSvnConfigDtoMapperImpl configToDtoMapper; + + @Mock + private SvnRepositoryHandler repositoryHandler; + + @Before + public void prepareEnvironment() { + SvnConfig gitConfig = createConfiguration(); + when(repositoryHandler.getConfig()).thenReturn(gitConfig); + SvnConfigResource gitConfigResource = new SvnConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); + dispatcher.getRegistry().addSingletonResource(gitConfigResource); + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldGetSvnConfig() throws URISyntaxException, IOException { + MockHttpResponse response = get(); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + String responseString = response.getContentAsString(); + ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class); + + assertTrue(responseString.contains("\"disabled\":false")); + assertTrue(responseJson.get("repositoryDirectory").asText().endsWith("repository/directory")); + assertTrue(responseString.contains("\"self\":{\"href\":\"/v2/config/svn")); + assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/svn")); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldGetSvnConfigEvenWhenItsEmpty() throws URISyntaxException, IOException { + when(repositoryHandler.getConfig()).thenReturn(null); + + MockHttpResponse response = get(); + String responseString = response.getContentAsString(); + + assertTrue(responseString.contains("\"disabled\":false")); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException { + MockHttpResponse response = get(); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + assertFalse(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/svn")); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + thrown.expectMessage("Subject does not have permission [configuration:read:svn]"); + + get(); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldUpdateConfig() throws URISyntaxException { + MockHttpResponse response = put(); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException { + thrown.expectMessage("Subject does not have permission [configuration:write:svn]"); + + put(); + } + + private MockHttpResponse get() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + SvnConfigResource.SVN_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private MockHttpResponse put() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.put("/" + SvnConfigResource.SVN_CONFIG_PATH_V2) + .contentType(SvnVndMediaType.SVN_CONFIG) + .content("{\"disabled\":true}".getBytes()); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private SvnConfig createConfiguration() { + SvnConfig config = new SvnConfig(); + config.setDisabled(false); + config.setRepositoryDirectory(new File("repository/directory")); + return config; + } + +} + diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java new file mode 100644 index 0000000000..de7b0ecd31 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java @@ -0,0 +1,95 @@ +package sonia.scm.api.v2.resources; + +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.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.Compatibility; +import sonia.scm.repository.SvnConfig; + +import java.io.File; +import java.net.URI; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SvnConfigToSvnConfigDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private SvnConfigToSvnConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + expectedBaseUri = baseUri.resolve(SvnConfigResource.SVN_CONFIG_PATH_V2); + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @After + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldMapFields() { + SvnConfig config = createConfiguration(); + + when(subject.isPermitted("configuration:write:svn")).thenReturn(true); + SvnConfigDto dto = mapper.map(config); + + assertTrue(dto.isDisabled()); + assertEquals("repository/directory", dto.getRepositoryDirectory().getPath()); + + assertEquals(Compatibility.PRE15, dto.getCompatibility()); + assertTrue(dto.isEnabledGZip()); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + @Test + public void shouldMapFieldsWithoutUpdate() { + SvnConfig config = createConfiguration(); + + when(subject.isPermitted("configuration:write:svn")).thenReturn(false); + SvnConfigDto dto = mapper.map(config); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertFalse(dto.getLinks().hasLink("update")); + } + + private SvnConfig createConfiguration() { + SvnConfig config = new SvnConfig(); + config.setDisabled(true); + config.setRepositoryDirectory(new File("repository/directory")); + + config.setCompatibility(Compatibility.PRE15); + config.setEnabledGZip(true); + + return config; + } + +} diff --git a/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini new file mode 100644 index 0000000000..7e4233b540 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini @@ -0,0 +1,9 @@ +[users] +readOnly = secret, reader +writeOnly = secret, writer +readWrite = secret, readerWriter + +[roles] +reader = configuration:read:svn +writer = configuration:write:svn +readerWriter = configuration:*:svn From 982d502203dc7e7bb865e2933278980e9477243c Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 13:18:21 +0200 Subject: [PATCH 24/45] Config Resources: Gets rid of unnecessary UriInfo param --- .../java/sonia/scm/api/v2/resources/GitConfigResource.java | 4 +--- .../java/sonia/scm/api/v2/resources/HgConfigResource.java | 6 ++---- .../java/sonia/scm/api/v2/resources/SvnConfigResource.java | 4 +--- .../java/sonia/scm/api/v2/resources/ConfigResource.java | 4 +--- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index 265a80ef6e..fe1f3c88d9 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -14,9 +14,7 @@ import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; /** * RESTful Web Service Resource to manage the configuration of the git plugin. @@ -79,7 +77,7 @@ public class GitConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@Context UriInfo uriInfo, GitConfigDto configDto) { + public Response update(GitConfigDto configDto) { GitConfig config = dtoToConfigMapper.map(configDto); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index 36838507ea..8966af91cd 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -14,9 +14,7 @@ import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; /** * RESTful Web Service Resource to manage the configuration of the hg plugin. @@ -79,7 +77,7 @@ public class HgConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@Context UriInfo uriInfo, HgConfigDto configDto) { + public Response update(HgConfigDto configDto) { HgConfig config = dtoToConfigMapper.map(configDto); @@ -91,8 +89,8 @@ public class HgConfigResource { return Response.noContent().build(); } + // TODO - //* `auto-configuration` // * `packages` // * `packages/{pkgId}` // * `installations/hg` diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java index c8050f41d3..bad9848ab5 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java @@ -14,9 +14,7 @@ import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; /** * RESTful Web Service Resource to manage the configuration of the svn plugin. @@ -79,7 +77,7 @@ public class SvnConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@Context UriInfo uriInfo, SvnConfigDto configDto) { + public Response update(SvnConfigDto configDto) { SvnConfig config = dtoToConfigMapper.map(configDto); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index 7bfa28f02b..9e61b50e86 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -14,9 +14,7 @@ import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; @Path(ConfigResource.CONFIG_PATH_V2) public class ConfigResource { @@ -70,7 +68,7 @@ public class ConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(ConfigDto configDto, @Context UriInfo uriInfo) { + public Response update(ConfigDto configDto) { // This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes. // But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later. From 988dfbd689ec9ceefe7e53155b676158440d718e Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 13:21:07 +0200 Subject: [PATCH 25/45] Config Resources: Makes REST API docs for 403 more precise --- .../java/sonia/scm/api/v2/resources/GitConfigResource.java | 4 ++-- .../java/sonia/scm/api/v2/resources/HgConfigResource.java | 4 ++-- .../java/sonia/scm/api/v2/resources/SvnConfigResource.java | 4 ++-- .../main/java/sonia/scm/api/v2/resources/ConfigResource.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index fe1f3c88d9..1384d73d9c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -45,7 +45,7 @@ public class GitConfigResource { @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the git config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:git\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) public Response get() { @@ -73,7 +73,7 @@ public class GitConfigResource { @StatusCodes({ @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:git\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index 8966af91cd..abd600fffb 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -45,7 +45,7 @@ public class HgConfigResource { @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the git config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) public Response get() { @@ -73,7 +73,7 @@ public class HgConfigResource { @StatusCodes({ @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java index bad9848ab5..b12785dca9 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java @@ -45,7 +45,7 @@ public class SvnConfigResource { @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the git config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:svn\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) public Response get() { @@ -73,7 +73,7 @@ public class SvnConfigResource { @StatusCodes({ @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:svn\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index 9e61b50e86..755e0c1c32 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -41,7 +41,7 @@ public class ConfigResource { @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the global config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:global\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) public Response get() { @@ -64,7 +64,7 @@ public class ConfigResource { @StatusCodes({ @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the global config"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:global\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) From 62087521be600297b86044f0b4dd64f721ff6629 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 14:16:26 +0200 Subject: [PATCH 26/45] Config Resources: Makes permission names publicly available --- .../src/main/java/sonia/scm/config/ScmConfiguration.java | 7 ++++--- .../src/main/java/sonia/scm/repository/GitConfig.java | 7 +++++-- .../src/main/java/sonia/scm/repository/HgConfig.java | 4 +++- .../src/main/java/sonia/scm/repository/SvnConfig.java | 5 ++++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index 6e1db68f91..e94fabfa60 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -90,9 +90,10 @@ public class ScmConfiguration implements Configuration { /** * the logger for ScmConfiguration */ - private static final Logger logger = - LoggerFactory.getLogger(ScmConfiguration.class); + private static final Logger logger = LoggerFactory.getLogger(ScmConfiguration.class); + @SuppressWarnings("WeakerAccess") // This might be needed for permission checking + public static final String PERMISSION = "global"; @XmlElement(name = "admin-groups") @XmlJavaTypeAdapter(XmlSetStringAdapter.class) @@ -509,6 +510,6 @@ public class ScmConfiguration implements Configuration { @XmlTransient public String getId() { // Don't change this without migrating SCM permission configuration! - return "global"; + return PERMISSION; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java index 3569fee179..03f38b0086 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java @@ -48,7 +48,10 @@ import javax.xml.bind.annotation.XmlTransient; @XmlRootElement(name = "config") @XmlAccessorType(XmlAccessType.FIELD) public class GitConfig extends RepositoryConfig { - + + @SuppressWarnings("WeakerAccess") // This might be needed for permission checking + public static final String PERMISSION = "git"; + @XmlElement(name = "gc-expression") private String gcExpression; @@ -65,6 +68,6 @@ public class GitConfig extends RepositoryConfig { @XmlTransient // Only for permission checks, don't serialize to XML public String getId() { // Don't change this without migrating SCM permission configuration! - return "git"; + return PERMISSION; } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java index 6438f49d4c..41b0f8d205 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java @@ -48,6 +48,8 @@ import javax.xml.bind.annotation.XmlTransient; public class HgConfig extends RepositoryConfig { + public static final String PERMISSION = "hg"; + /** * Constructs ... * @@ -227,6 +229,6 @@ public class HgConfig extends RepositoryConfig @XmlTransient // Only for permission checks, don't serialize to XML public String getId() { // Don't change this without migrating SCM permission configuration! - return "hg"; + return PERMISSION; } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java index 73b4f39219..5fe5c0815d 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java @@ -48,6 +48,9 @@ import javax.xml.bind.annotation.XmlTransient; public class SvnConfig extends RepositoryConfig { + @SuppressWarnings("WeakerAccess") // This might be needed for permission checking + public static final String PERMISSION = "svn"; + /** * Method description * @@ -112,6 +115,6 @@ public class SvnConfig extends RepositoryConfig @XmlTransient // Only for permission checks, don't serialize to XML public String getId() { // Don't change this without migrating SCM permission configuration! - return "svn"; + return PERMISSION; } } From e28faf302971d088b3eee93b606b6ee0e79e4025 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 14:45:04 +0200 Subject: [PATCH 27/45] Create CollectionToDtoMapper in core module. See commit 689e5ae95645768b024dee52c10451c944a41527. Useful for hg plugin. --- pom.xml | 19 +++++++++++ scm-core/pom.xml | 21 ++++++++++-- .../scm/api/v2/resources/BaseMapper.java | 3 +- .../v2/resources/CollectionToDtoMapper.java | 32 +++++++++++++++++++ scm-plugins/pom.xml | 17 ---------- scm-webapp/pom.xml | 16 ---------- 6 files changed, 70 insertions(+), 38 deletions(-) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java (77%) create mode 100644 scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java diff --git a/pom.xml b/pom.xml index 174fa07079..59d08a375c 100644 --- a/pom.xml +++ b/pom.xml @@ -252,6 +252,25 @@ ${resteasy.version} + + javax.ws.rs + javax.ws.rs-api + ${jaxrs.version} + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + diff --git a/scm-core/pom.xml b/scm-core/pom.xml index f19ad7453e..a25214da7e 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -85,17 +85,32 @@ javax.ws.rs javax.ws.rs-api - ${jaxrs.version} + com.fasterxml.jackson.core jackson-core - ${jackson.version} + com.fasterxml.jackson.core jackson-databind - ${jackson.version} + + + + de.otto.edison + edison-hal + + + + org.mapstruct + mapstruct-jdk8 + + + + org.mapstruct + mapstruct-processor + provided diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java similarity index 77% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java index 94d884c437..a3f83d21ab 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java @@ -2,11 +2,10 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; import org.mapstruct.Mapping; -import sonia.scm.ModelObject; import java.time.Instant; -abstract class BaseMapper { +abstract class BaseMapper { @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract D map(T modelObject); diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java new file mode 100644 index 0000000000..4f8c6a6f3f --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java @@ -0,0 +1,32 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Links.linkingTo; + +abstract class CollectionToDtoMapper { + + private final String collectionName; + private final BaseMapper mapper; + + protected CollectionToDtoMapper(String collectionName, BaseMapper mapper) { + this.collectionName = collectionName; + this.mapper = mapper; + } + + public HalRepresentation map(Collection collection) { + List dtos = collection.stream().map(mapper::map).collect(Collectors.toList()); + return new HalRepresentation( + linkingTo().self(createSelfLink()).build(), + embeddedBuilder().with(collectionName, dtos).build() + ); + } + + protected abstract String createSelfLink(); + +} diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 70dee9676a..e82eaddb9a 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -63,23 +63,6 @@ enunciate-core-annotations - - org.mapstruct - mapstruct-jdk8 - - - - org.mapstruct - mapstruct-processor - provided - - - - de.otto.edison - edison-hal - compile - - org.projectlombok lombok diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 538eba4911..30944d49cc 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -150,11 +150,6 @@ resteasy-servlet-initializer - - de.otto.edison - edison-hal - - @@ -383,17 +378,6 @@ provided - - org.mapstruct - mapstruct-jdk8 - - - - org.mapstruct - mapstruct-processor - provided - - From 8c8d9083783a3119d4284bf3b7d660252cf98ccc Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 15:03:30 +0200 Subject: [PATCH 28/45] Config Resource DTO Mappers: Make use of BaseMapper. More DRY. --- .../scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java | 6 +----- .../scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java | 6 +----- .../scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java | 6 +----- .../api/v2/resources/ScmConfigurationToConfigDtoMapper.java | 6 +----- 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java index a4eb8e29a7..7163497487 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.GitConfig; @@ -16,14 +15,11 @@ import static de.otto.edison.hal.Links.linkingTo; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") @Mapper -public abstract class GitConfigToGitConfigDtoMapper { +public abstract class GitConfigToGitConfigDtoMapper extends BaseMapper { @Inject private UriInfoStore uriInfoStore; - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - public abstract GitConfigDto map(GitConfig config); - @AfterMapping void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) { Links.Builder linksBuilder = linkingTo().self(self()); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java index 464f32a5a4..98137aebd5 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapper.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.HgConfig; @@ -16,14 +15,11 @@ import static de.otto.edison.hal.Links.linkingTo; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") @Mapper -public abstract class HgConfigToHgConfigDtoMapper { +public abstract class HgConfigToHgConfigDtoMapper extends BaseMapper { @Inject private UriInfoStore uriInfoStore; - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - public abstract HgConfigDto map(HgConfig config); - @AfterMapping void appendLinks(HgConfig config, @MappingTarget HgConfigDto target) { Links.Builder linksBuilder = linkingTo().self(self()); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java index df974c3f3d..a71d75151d 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapper.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.SvnConfig; @@ -16,14 +15,11 @@ import static de.otto.edison.hal.Links.linkingTo; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") @Mapper -public abstract class SvnConfigToSvnConfigDtoMapper { +public abstract class SvnConfigToSvnConfigDtoMapper extends BaseMapper { @Inject private UriInfoStore uriInfoStore; - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - public abstract SvnConfigDto map(SvnConfig config); - @AfterMapping void appendLinks(SvnConfig config, @MappingTarget SvnConfigDto target) { Links.Builder linksBuilder = linkingTo().self(self()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java index ee0d32b0f8..ac9dba4e41 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.config.ScmConfiguration; @@ -16,14 +15,11 @@ import static de.otto.edison.hal.Links.linkingTo; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") @Mapper -public abstract class ScmConfigurationToConfigDtoMapper { +public abstract class ScmConfigurationToConfigDtoMapper extends BaseMapper { @Inject private ResourceLinks resourceLinks; - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - public abstract ConfigDto map(ScmConfiguration config); - @AfterMapping void appendLinks(ScmConfiguration config, @MappingTarget ConfigDto target) { Links.Builder linksBuilder = linkingTo().self(resourceLinks.config().self()); From 1c52b33229715b60e64d0a32f03aa26691046372 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 15:22:41 +0200 Subject: [PATCH 29/45] Brings mapstruct processor back to plugin and webapp. --- scm-core/pom.xml | 6 ------ scm-plugins/pom.xml | 9 ++++++++- scm-webapp/pom.xml | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/scm-core/pom.xml b/scm-core/pom.xml index a25214da7e..a804af1109 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -107,12 +107,6 @@ mapstruct-jdk8 - - org.mapstruct - mapstruct-processor - provided - - diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index e82eaddb9a..8cdbd877b6 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -38,7 +38,7 @@ provided - + sonia.scm @@ -46,6 +46,13 @@ 2.0.0-SNAPSHOT provided + + + + org.mapstruct + mapstruct-processor + provided + diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 30944d49cc..a1ec0fe926 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -378,6 +378,12 @@ provided + + org.mapstruct + mapstruct-processor + provided + + From 7107d14bce8624c28d3523ecd86fd651e3c28b37 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 18:33:36 +0200 Subject: [PATCH 30/45] Makes BaseMapper and CollectionToDtoMapper available for plugins --- .../src/main/java/sonia/scm/api/v2/resources/BaseMapper.java | 4 ++-- .../sonia/scm/api/v2/resources/CollectionToDtoMapper.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java index a3f83d21ab..e4cf8ecb5d 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java @@ -5,12 +5,12 @@ import org.mapstruct.Mapping; import java.time.Instant; -abstract class BaseMapper { +public abstract class BaseMapper { @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract D map(T modelObject); - Instant mapTime(Long epochMilli) { + protected Instant mapTime(Long epochMilli) { return epochMilli == null? null: Instant.ofEpochMilli(epochMilli); } } diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java index 4f8c6a6f3f..523629ce3b 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java @@ -9,7 +9,7 @@ import java.util.stream.Collectors; import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Links.linkingTo; -abstract class CollectionToDtoMapper { +public abstract class CollectionToDtoMapper { private final String collectionName; private final BaseMapper mapper; From b65a8c6b8da4886228875ccdf07948c9205ea6c3 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Thu, 2 Aug 2018 18:36:28 +0200 Subject: [PATCH 31/45] Implements Hg Config Sub Resources --- .../HgConfigAutoConfigurationResource.java | 68 +++++++++++++ .../resources/HgConfigInstallationsDto.java | 22 +++++ .../HgConfigInstallationsResource.java | 65 +++++++++++++ .../HgConfigInstallationsToDtoMapper.java | 21 ++++ .../HgConfigPackageCollectionToDtoMapper.java | 22 +++++ .../api/v2/resources/HgConfigPackageDto.java | 28 ++++++ .../v2/resources/HgConfigPackageResource.java | 96 +++++++++++++++++++ .../resources/HgConfigPackageToDtoMapper.java | 28 ++++++ .../api/v2/resources/HgConfigResource.java | 34 +++++-- .../java/sonia/scm/web/HgServletModule.java | 6 ++ .../java/sonia/scm/web/HgVndMediaType.java | 6 +- .../v2/resources/HgConfigResourceTest.java | 18 +++- 12 files changed, 402 insertions(+), 12 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java new file mode 100644 index 0000000000..2198262275 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java @@ -0,0 +1,68 @@ +package sonia.scm.api.v2.resources; + +import com.google.inject.Inject; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; + +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +public class HgConfigAutoConfigurationResource { + + private final HgRepositoryHandler repositoryHandler; + + @Inject + public HgConfigAutoConfigurationResource(HgRepositoryHandler repositoryHandler) { + this.repositoryHandler = repositoryHandler; + } + + /** + * Sets the default hg config and installs the hg binary. + */ + @PUT + @Path("") + @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 \"configuration:write:hg\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response autoConfiguration() { + return autoConfiguration(null); + } + + /** + * Modifies the hg config and installs the hg binary. + * + * @param configDto new configuration object + */ + @PUT + @Path("") + @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 \"configuration:write:hg\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response autoConfiguration(HgConfigDto configDto) { + HgConfig config = repositoryHandler.getConfig(); + + if (config == null) { + config = new HgConfig(); + repositoryHandler.setConfig(config); + } + + ConfigurationPermissions.write(config).check(); + + repositoryHandler.doAutoConfiguration(config); + + return Response.noContent().build(); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java new file mode 100644 index 0000000000..be97e2972b --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@NoArgsConstructor +@Getter +@Setter +public class HgConfigInstallationsDto extends HalRepresentation { + + public HgConfigInstallationsDto(Links links, List paths) { + super(links); + this.paths = paths; + } + + private List paths; +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java new file mode 100644 index 0000000000..e1afd8c3b2 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java @@ -0,0 +1,65 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import de.otto.edison.hal.HalRepresentation; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.installer.HgInstallerFactory; +import sonia.scm.repository.HgConfig; +import sonia.scm.web.HgVndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +public class HgConfigInstallationsResource { + + private final HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper; + + @Inject + public HgConfigInstallationsResource(HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper) { + this.hgConfigInstallationsToDtoMapper = hgConfigInstallationsToDtoMapper; + } + + /** + * Returns the hg installations. + */ + @GET + @Path("hg") + @Produces(HgVndMediaType.INSTALLATIONS) + @TypeHint(HalRepresentation.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public HalRepresentation getHgInstallations() { + + ConfigurationPermissions.read(HgConfig.PERMISSION).check(); + + return hgConfigInstallationsToDtoMapper.map(HgInstallerFactory.createInstaller().getHgInstallations()); + } + + /** + * Returns the python installations. + */ + @GET + @Path("python") + @Produces(HgVndMediaType.INSTALLATIONS) + @TypeHint(HalRepresentation.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public HalRepresentation getPythonInstallations() { + + ConfigurationPermissions.read(HgConfig.PERMISSION).check(); + + return hgConfigInstallationsToDtoMapper.map(HgInstallerFactory.createInstaller().getPythonInstallations()); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java new file mode 100644 index 0000000000..767c05abbe --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java @@ -0,0 +1,21 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; + +import javax.inject.Inject; +import java.util.List; + +import static de.otto.edison.hal.Links.linkingTo; + +public class HgConfigInstallationsToDtoMapper { + @Inject private UriInfoStore uriInfoStore; + + public HalRepresentation map(List installations) { + return new HgConfigInstallationsDto(linkingTo().self(createSelfLink()).build(), installations); + } + + private String createSelfLink() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigInstallationsResource.class); + return linkBuilder.method("get").parameters().href(); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java new file mode 100644 index 0000000000..abba4c5201 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.installer.HgPackage; + +import javax.inject.Inject; + +public class HgConfigPackageCollectionToDtoMapper extends CollectionToDtoMapper { + + private UriInfoStore uriInfoStore; + + @Inject + public HgConfigPackageCollectionToDtoMapper(HgConfigPackageToDtoMapper mapper, UriInfoStore uriInfoStore) { + super("packages", mapper); + this.uriInfoStore = uriInfoStore; + } + + @Override + protected String createSelfLink() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); + return linkBuilder.method("get").parameters().href(); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java new file mode 100644 index 0000000000..ed32ea398f --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java @@ -0,0 +1,28 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import sonia.scm.repository.HgConfig; + +@NoArgsConstructor +@Getter +@Setter +public class HgConfigPackageDto extends HalRepresentation { + + private String arch; + private HgConfig hgConfigTemplate; + private String hgVersion; + private String id; + private String platform; + private String pythonVersion; + private long size; + private String url; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java new file mode 100644 index 0000000000..837281726e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java @@ -0,0 +1,96 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import de.otto.edison.hal.HalRepresentation; +import sonia.scm.SCMContext; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.installer.HgInstallerFactory; +import sonia.scm.installer.HgPackage; +import sonia.scm.installer.HgPackageReader; +import sonia.scm.net.ahc.AdvancedHttpClient; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.web.HgVndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +public class HgConfigPackageResource { + + private final HgPackageReader pkgReader; + private final AdvancedHttpClient client; + private final HgRepositoryHandler handler; + private final HgConfigPackageCollectionToDtoMapper configPackageCollectionToDtoMapper; + + @Inject + public HgConfigPackageResource(HgPackageReader pkgReader, AdvancedHttpClient client, HgRepositoryHandler handler, + HgConfigPackageCollectionToDtoMapper configPackageCollectionToDtoMapper) { + this.pkgReader = pkgReader; + this.client = client; + this.handler = handler; + this.configPackageCollectionToDtoMapper = configPackageCollectionToDtoMapper; + } + + /** + * Returns all mercurial packages. + */ + @GET + @Path("") + @Produces(HgVndMediaType.PACKAGES) + @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 \"configuration:read:hg\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(HalRepresentation.class) + public HalRepresentation getPackages() { + + ConfigurationPermissions.read(HgConfig.PERMISSION).check(); + + return configPackageCollectionToDtoMapper.map(pkgReader.getPackages().getPackages()); + } + + /** + * Installs a mercurial package + * + * @param id Identifier of the package to install + */ + @PUT + @Path("{pkgId}") + @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 \"configuration:write:hg\" privilege"), + @ResponseCode(code = 404, condition = "no package found for id"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response installPackage(@PathParam("pkgId") String id) { + Response response; + + ConfigurationPermissions.write(HgConfig.PERMISSION).check(); + + HgPackage pkg = pkgReader.getPackage(id); + + if (pkg != null) { + if (HgInstallerFactory.createInstaller().installPackage(client, handler, + SCMContext.getContext().getBaseDirectory(), pkg)) { + response = Response.noContent().build(); + } else { + response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } else { + response = Response.status(Response.Status.NOT_FOUND).build(); + } + + return response; + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java new file mode 100644 index 0000000000..7fac8a9c3b --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java @@ -0,0 +1,28 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import sonia.scm.installer.HgPackage; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class HgConfigPackageToDtoMapper extends BaseMapper { + @Inject + private UriInfoStore uriInfoStore; + + @AfterMapping + void appendLinks(@MappingTarget HgConfigPackageDto target) { + Links.Builder linksBuilder = linkingTo().self(self()); + target.add(linksBuilder.build()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigPackageResource.class); + return linkBuilder.method("get").parameters().href(); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index abd600fffb..f98300251f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -9,6 +9,7 @@ import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; import javax.inject.Inject; +import javax.inject.Provider; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; @@ -23,16 +24,25 @@ import javax.ws.rs.core.Response; public class HgConfigResource { static final String HG_CONFIG_PATH_V2 = "v2/config/hg"; + private final HgConfigDtoToHgConfigMapper dtoToConfigMapper; private final HgConfigToHgConfigDtoMapper configToDtoMapper; private final HgRepositoryHandler repositoryHandler; + private final Provider packagesResource; + private final Provider autoconfigResource; + private final Provider installationsResource; @Inject public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper, - HgRepositoryHandler repositoryHandler) { + HgRepositoryHandler repositoryHandler, Provider packagesResource, + Provider autoconfigResource, + Provider installationsResource) { this.dtoToConfigMapper = dtoToConfigMapper; this.configToDtoMapper = configToDtoMapper; this.repositoryHandler = repositoryHandler; + this.packagesResource = packagesResource; + this.autoconfigResource = autoconfigResource; + this.installationsResource = installationsResource; } /** @@ -40,7 +50,7 @@ public class HgConfigResource { */ @GET @Path("") - @Produces(HgVndMediaType.HG_CONFIG) + @Produces(HgVndMediaType.CONFIG) @TypeHint(HgConfigDto.class) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @@ -69,7 +79,7 @@ public class HgConfigResource { */ @PUT @Path("") - @Consumes(HgVndMediaType.HG_CONFIG) + @Consumes(HgVndMediaType.CONFIG) @StatusCodes({ @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @@ -89,10 +99,18 @@ public class HgConfigResource { return Response.noContent().build(); } + @Path("packages") + public HgConfigPackageResource getPackagesResource() { + return packagesResource.get(); + } - // TODO - // * `packages` - // * `packages/{pkgId}` - // * `installations/hg` - // * `installations/python + @Path("auto-configuration") + public HgConfigAutoConfigurationResource getAutoConfigurationResource() { + return autoconfigResource.get(); + } + + @Path("installations") + public HgConfigInstallationsResource getInstallationsResource() { + return installationsResource.get(); + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index b9cf1e150d..dd1fbd4748 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -38,6 +38,9 @@ package sonia.scm.web; import com.google.inject.servlet.ServletModule; import org.mapstruct.factory.Mappers; import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper; +import sonia.scm.api.v2.resources.HgConfigInstallationsToDtoMapper; +import sonia.scm.api.v2.resources.HgConfigPackageCollectionToDtoMapper; +import sonia.scm.api.v2.resources.HgConfigPackageToDtoMapper; import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper; import sonia.scm.installer.HgPackageReader; import sonia.scm.plugin.Extension; @@ -74,6 +77,9 @@ public class HgServletModule extends ServletModule bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass()); bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass()); + bind(HgConfigPackageToDtoMapper.class).to(Mappers.getMapper(HgConfigPackageToDtoMapper.class).getClass()); + bind(HgConfigPackageCollectionToDtoMapper.class); + bind(HgConfigInstallationsToDtoMapper.class); // bind servlets serve(MAPPING_HOOK).with(HgHookCallbackServlet.class); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java index 6f31090d58..033c5e8361 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgVndMediaType.java @@ -2,7 +2,11 @@ package sonia.scm.web; public class HgVndMediaType { - public static final String HG_CONFIG = VndMediaType.PREFIX + "hgConfig" + VndMediaType.SUFFIX; + private static final String PREFIX = VndMediaType.PREFIX + "hgConfig"; + + public static final String CONFIG = PREFIX + VndMediaType.SUFFIX; + public static final String PACKAGES = PREFIX + "-packages" + VndMediaType.SUFFIX; + public static final String INSTALLATIONS = PREFIX + "-installation" + VndMediaType.SUFFIX; private HgVndMediaType() { } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index f260a9f6d1..c655e8433a 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -21,6 +21,7 @@ import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; +import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; @@ -61,11 +62,22 @@ public class HgConfigResourceTest { @Mock private HgRepositoryHandler repositoryHandler; + @Mock + private Provider packagesResource; + + @Mock + private Provider autoconfigResource; + + @Mock + private Provider installationsResource; + @Before public void prepareEnvironment() { HgConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); - HgConfigResource gitConfigResource = new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); + HgConfigResource gitConfigResource = + new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource, + autoconfigResource, installationsResource); dispatcher.getRegistry().addSingletonResource(gitConfigResource); when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); } @@ -88,7 +100,7 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readWrite") - public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, IOException { + public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException { when(repositoryHandler.getConfig()).thenReturn(null); MockHttpResponse response = get(); @@ -139,7 +151,7 @@ public class HgConfigResourceTest { private MockHttpResponse put() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.HG_CONFIG_PATH_V2) - .contentType(HgVndMediaType.HG_CONFIG) + .contentType(HgVndMediaType.CONFIG) .content("{\"disabled\":true}".getBytes()); MockHttpResponse response = new MockHttpResponse(); From a07c9c18dd81cd05171915044b706316cd4645fc Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 11:36:08 +0200 Subject: [PATCH 32/45] Gets rid of redundant dependency declarations in plugins and webapp. It's better to define the "web"/REST dependencies that are used by plugins once in core, instead of redundant in webapp and plugins. This should create better awareness when changing the dependency version (changing in core is always critical due to API downward compatibility). --- pom.xml | 1 - scm-core/pom.xml | 12 +++++++++++- scm-plugins/pom.xml | 47 ++++++++------------------------------------- scm-webapp/pom.xml | 34 +++++++------------------------- 4 files changed, 26 insertions(+), 68 deletions(-) diff --git a/pom.xml b/pom.xml index 59d08a375c..6a759e848b 100644 --- a/pom.xml +++ b/pom.xml @@ -270,7 +270,6 @@ ${jackson.version} - diff --git a/scm-core/pom.xml b/scm-core/pom.xml index a804af1109..79d456be9e 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -79,7 +79,7 @@ guice-throwingproviders ${guice.version} - + @@ -87,6 +87,8 @@ javax.ws.rs-api + + com.fasterxml.jackson.core jackson-core @@ -97,16 +99,24 @@ jackson-databind + de.otto.edison edison-hal + org.mapstruct mapstruct-jdk8 + + + com.webcohesion.enunciate + enunciate-core-annotations + + diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 8cdbd877b6..4d27edd75a 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -53,6 +53,13 @@ mapstruct-processor provided + + + + org.projectlombok + lombok + provided + @@ -63,50 +70,12 @@ test - - - - com.webcohesion.enunciate - enunciate-core-annotations - - - - org.projectlombok - lombok - provided - - - - org.jboss.resteasy resteasy-jaxrs + test - - org.jboss.resteasy - resteasy-jaxb-provider - - - - org.jboss.resteasy - resteasy-jackson2-provider - - - - org.jboss.resteasy - resteasy-multipart-provider - - - - org.jboss.resteasy - resteasy-guice - - - - org.jboss.resteasy - resteasy-servlet-initializer - diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index a1ec0fe926..85e53dae1a 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -75,33 +75,21 @@ jjwt 0.4 - + - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - com.fasterxml.jackson.core jackson-annotations ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - + com.fasterxml.jackson.module jackson-module-jaxb-annotations ${jackson.version} - + com.fasterxml.jackson.jaxrs jackson-jaxrs-base @@ -119,7 +107,7 @@ - + org.jboss.resteasy resteasy-jaxrs @@ -144,7 +132,7 @@ org.jboss.resteasy resteasy-guice - + org.jboss.resteasy resteasy-servlet-initializer @@ -157,7 +145,7 @@ guice-multibindings ${guice.version} - + @@ -245,13 +233,6 @@ ${mustache.version} - - - - com.webcohesion.enunciate - enunciate-core-annotations - - @@ -303,8 +284,7 @@ - - + com.github.sdorra shiro-unit From 0179cc3369ac0c94481f5770ee2798a6f703ad41 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 11:52:31 +0200 Subject: [PATCH 33/45] Adds profile for rest docs to plugins module. It's redundant (also configured in webapp) but would fail the core build if activated. --- pom.xml | 17 ++++++++- scm-plugins/pom.xml | 90 +++++++++++++++++++++++++++++++++++++++++++++ scm-server/pom.xml | 1 - scm-webapp/pom.xml | 45 +++++++++++------------ 4 files changed, 127 insertions(+), 26 deletions(-) diff --git a/pom.xml b/pom.xml index 6a759e848b..ff85e67ad6 100644 --- a/pom.xml +++ b/pom.xml @@ -282,6 +282,22 @@ maven-javadoc-plugin 3.0.1 + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + + org.apache.maven.plugins + maven-assembly-plugin + 2.3 + + + + com.webcohesion.enunciate + enunciate-maven-plugin + ${enunciate.version} + @@ -382,7 +398,6 @@ org.apache.maven.plugins maven-resources-plugin - 2.6 ${project.build.sourceEncoding} diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 4d27edd75a..3a73bad478 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -161,6 +161,96 @@ + + doc + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-enunciate-configuration + compile + + copy-resources + + + ${project.build.directory} + + + src/main/doc + true + + **/enunciate.xml + + + + + + + + + + com.webcohesion.enunciate + enunciate-maven-plugin + + + + docs + + compile + + + + ${project.build.directory}/enunciate.xml + ${project.build.directory} + restdocs + + + + com.webcohesion.enunciate + enunciate-top + ${enunciate.version} + + + com.webcohesion.enunciate + enunciate-swagger + + + + + com.webcohesion.enunciate + enunciate-lombok + ${enunciate.version} + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + src/main/doc/assembly.xml + + + + + package + + single + + + + + + + + + diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 64172d6673..c8d004cec1 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -141,7 +141,6 @@ org.apache.maven.plugins maven-assembly-plugin - 2.3 src/main/assembly/scm-server-app.xml diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 85e53dae1a..80342a52bb 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -645,29 +645,29 @@ - + - + selenium - + - + org.apache.httpcomponents httpclient 4.3.2 test - + - + - + org.apache.maven.plugins maven-failsafe-plugin @@ -692,7 +692,7 @@ - + org.eclipse.jetty jetty-maven-plugin @@ -730,7 +730,7 @@ - + org.codehaus.mojo selenium-maven-plugin @@ -751,26 +751,25 @@ post-integration-test stop-server - + - + - + - + doc - + - + org.apache.maven.plugins maven-resources-plugin - 2.6 copy-enunciate-configuration @@ -780,7 +779,7 @@ ${project.build.directory} - + src/main/doc true @@ -788,16 +787,15 @@ **/enunciate.xml - - + + - + com.webcohesion.enunciate enunciate-maven-plugin - ${enunciate.version} @@ -830,11 +828,10 @@ - + org.apache.maven.plugins maven-assembly-plugin - 2.3 src/main/doc/assembly.xml @@ -849,7 +846,7 @@ - + From fc0676d5119a974caeb05c070a820f3b54e95433 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 12:57:44 +0200 Subject: [PATCH 34/45] Moves jackson annotations dependency to core. Will be needed by plugins in future. --- pom.xml | 6 ++++++ scm-core/pom.xml | 5 +++++ scm-webapp/pom.xml | 6 ------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index ff85e67ad6..fd5f2ac0ee 100644 --- a/pom.xml +++ b/pom.xml @@ -270,6 +270,12 @@ ${jackson.version} + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 79d456be9e..17ca03b115 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -99,6 +99,11 @@ jackson-databind + + com.fasterxml.jackson.core + jackson-annotations + + de.otto.edison diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 80342a52bb..d0003c1f7c 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -78,12 +78,6 @@ - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - com.fasterxml.jackson.module jackson-module-jaxb-annotations From d327298bd1a7ccc68231075576fcba73a2afc317 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 13:47:04 +0200 Subject: [PATCH 35/45] Adds jackson2 resteasy provider to plugins for testing. Otherwise the tests using Resteasy mocks will fail with: "org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object" --- scm-plugins/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 3a73bad478..f9065babc2 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -76,6 +76,12 @@ test + + org.jboss.resteasy + resteasy-jackson2-provider + test + + From 3d13d8ad1ce3cd3bed6d0b789d2ae460c57ae085 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 14:08:57 +0200 Subject: [PATCH 36/45] Introduces assertJ for more efficient and maintainable testing. --- pom.xml | 46 +++++++++++++++++++++++++++++++++++++++++----- scm-test/pom.xml | 4 ++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index fd5f2ac0ee..d1b2f4f690 100644 --- a/pom.xml +++ b/pom.xml @@ -120,28 +120,31 @@ junit junit - ${junit.version} test org.hamcrest hamcrest-core - ${hamcrest.version} test org.hamcrest hamcrest-library - ${hamcrest.version} test org.mockito mockito-all - ${mokito.version} + test + + + + org.assertj + assertj-core + 3.10.0 test @@ -204,7 +207,6 @@ de.otto.edison edison-hal 2.0.1 - compile @@ -276,6 +278,40 @@ ${jackson.version} + + junit + junit + ${junit.version} + test + + + + org.hamcrest + hamcrest-core + ${hamcrest.version} + test + + + + org.hamcrest + hamcrest-library + ${hamcrest.version} + test + + + + org.mockito + mockito-all + ${mokito.version} + test + + + + org.assertj + assertj-core + 3.10.0 + test + diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 7556fa4a80..9bc5019a1d 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -31,7 +31,7 @@ junit junit - ${junit.version} + compile @@ -44,7 +44,7 @@ org.mockito mockito-all - ${mokito.version} + compile From c3713690321516d3f20056591c895f1a037ed5ef Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 14:09:24 +0200 Subject: [PATCH 37/45] Maven: Don't package ces-build-lib into plugins. --- pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.xml b/pom.xml index d1b2f4f690..1753e82f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -156,6 +156,8 @@ 9aadeeb true + + provided From 7d59975c80aeb10e65195ed812b897363c27dae6 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 16:21:46 +0200 Subject: [PATCH 38/45] Adds test for HgConfigPackageResource, its DTO and mappers. --- .../HgConfigPackageCollectionToDtoMapper.java | 5 +- .../api/v2/resources/HgConfigPackageDto.java | 3 +- .../v2/resources/HgConfigPackageResource.java | 19 +- .../resources/HgConfigPackageToDtoMapper.java | 20 +- .../scm/installer/HgPackageInstaller.java | 1 + ...onfigPackageCollectionToDtoMapperTest.java | 77 +++++++ .../HgConfigPackageResourceTest.java | 217 ++++++++++++++++++ .../HgConfigPackageToDtoMapperTest.java | 43 ++++ .../scm/api/v2/resources/HgConfigTests.java | 71 ++++++ .../HgConfigToHgConfigDtoMapperTest.java | 30 +-- 10 files changed, 430 insertions(+), 56 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java index abba4c5201..5e69c8b199 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java @@ -6,17 +6,18 @@ import javax.inject.Inject; public class HgConfigPackageCollectionToDtoMapper extends CollectionToDtoMapper { + static final String COLLECTION_NAME = "packages"; private UriInfoStore uriInfoStore; @Inject public HgConfigPackageCollectionToDtoMapper(HgConfigPackageToDtoMapper mapper, UriInfoStore uriInfoStore) { - super("packages", mapper); + super(COLLECTION_NAME, mapper); this.uriInfoStore = uriInfoStore; } @Override protected String createSelfLink() { LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); - return linkBuilder.method("get").parameters().href(); + return linkBuilder.method("getPackagesResource").parameters().href(); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java index ed32ea398f..bd73b7d76c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java @@ -5,7 +5,6 @@ import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import sonia.scm.repository.HgConfig; @NoArgsConstructor @Getter @@ -13,7 +12,7 @@ import sonia.scm.repository.HgConfig; public class HgConfigPackageDto extends HalRepresentation { private String arch; - private HgConfig hgConfigTemplate; + private HgConfigDto hgConfigTemplate; private String hgVersion; private String id; private String platform; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java index 837281726e..a2fca37ff2 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java @@ -61,7 +61,7 @@ public class HgConfigPackageResource { /** * Installs a mercurial package * - * @param id Identifier of the package to install + * @param pkgId Identifier of the package to install */ @PUT @Path("{pkgId}") @@ -73,22 +73,19 @@ public class HgConfigPackageResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response installPackage(@PathParam("pkgId") String id) { + public Response installPackage(@PathParam("pkgId") String pkgId) { Response response; ConfigurationPermissions.write(HgConfig.PERMISSION).check(); - HgPackage pkg = pkgReader.getPackage(id); + HgPackage pkg = pkgReader.getPackage(pkgId); - if (pkg != null) { - if (HgInstallerFactory.createInstaller().installPackage(client, handler, - SCMContext.getContext().getBaseDirectory(), pkg)) { - response = Response.noContent().build(); - } else { - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } + // First path parm cannot be null (leaving it results in 405) + if (HgInstallerFactory.createInstaller() + .installPackage(client, handler, SCMContext.getContext().getBaseDirectory(), pkg)) { + response = Response.noContent().build(); } else { - response = Response.status(Response.Status.NOT_FOUND).build(); + response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } return response; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java index 7fac8a9c3b..31671e3e14 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java @@ -1,28 +1,20 @@ package sonia.scm.api.v2.resources; -import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.MappingTarget; +import org.mapstruct.Mapping; import sonia.scm.installer.HgPackage; import javax.inject.Inject; -import static de.otto.edison.hal.Links.linkingTo; - @Mapper public abstract class HgConfigPackageToDtoMapper extends BaseMapper { @Inject private UriInfoStore uriInfoStore; - @AfterMapping - void appendLinks(@MappingTarget HgConfigPackageDto target) { - Links.Builder linksBuilder = linkingTo().self(self()); - target.add(linksBuilder.build()); - } + @Override + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + @Mapping(target = "hgConfigTemplate.attributes", ignore = true) // Also not for nested DTOs + public abstract HgConfigPackageDto map(HgPackage modelObject); - private String self() { - LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigPackageResource.class); - return linkBuilder.method("get").parameters().href(); - } + // Don't add links because ConfigPackages don't have their own ressource } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java index 7406505b5a..763977cfa3 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java @@ -162,6 +162,7 @@ public class HgPackageInstaller implements Runnable catch (IOException ex) { logger.error("could not downlaod file ".concat(pkg.getUrl()), ex); + file = null; } finally { diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java new file mode 100644 index 0000000000..27cd3c73c3 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java @@ -0,0 +1,77 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.installer.HgPackage; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.HgConfigPackageCollectionToDtoMapper.COLLECTION_NAME; +import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage; +import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; + +@RunWith(MockitoJUnitRunner.class) +public class HgConfigPackageCollectionToDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private HgConfigPackageToDtoMapperImpl hgConfigPackageToDtoMapper; + + private HgConfigPackageCollectionToDtoMapper mapper; + + private URI expectedBaseUri; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/packages"); + mapper = new HgConfigPackageCollectionToDtoMapper(hgConfigPackageToDtoMapper, uriInfoStore); + } + + @Test + public void shouldMapFields() { + Collection hgPackages = createPackages(); + + HalRepresentation dto = mapper.map(hgPackages); + + List itemsBy = dto.getEmbedded().getItemsBy(COLLECTION_NAME); + assertThat(itemsBy).hasSize(2); + + HgConfigPackageDto hgPackageDto1 = assertAndGetAsDto(itemsBy.get(0)); + assertEqualsPackage(hgPackageDto1); + + HgConfigPackageDto hgPackageDto2 = assertAndGetAsDto(itemsBy.get(1)); + assertTrue(hgPackageDto2.getLinks().isEmpty()); + // Just verify a random field + assertThat(hgPackageDto2.getId()).isNull(); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + } + + private HgConfigPackageDto assertAndGetAsDto(HalRepresentation halRepresentation) { + assertThat(halRepresentation).isInstanceOf(HgConfigPackageDto.class); + return (HgConfigPackageDto) halRepresentation; + } + + private Collection createPackages() { + return Arrays.asList(createPackage(), new HgPackage()); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java new file mode 100644 index 0000000000..0bb7d3e048 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -0,0 +1,217 @@ +package sonia.scm.api.v2.resources; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.installer.HgPackage; +import sonia.scm.installer.HgPackageReader; +import sonia.scm.net.ahc.AdvancedHttpClient; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; + +import javax.inject.Provider; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; + +@SubjectAware( + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" +) +@RunWith(MockitoJUnitRunner.class) +public class HgConfigPackageResourceTest { + + public static final String URI = "/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/packages"; + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder folder= new TemporaryFolder(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = java.net.URI.create("/"); + + @InjectMocks + private HgConfigPackageToDtoMapperImpl hgConfigPackageToDtoMapper; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @Mock + private HgRepositoryHandler repositoryHandler; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private HgPackageReader hgPackageReader; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private AdvancedHttpClient advancedHttpClient; + + @Mock + private Provider hgConfigPackageResourceProvider; + + @Mock + private HgPackage hgPackage; + + @Before + public void prepareEnvironment() { + setupResources(); + + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + + when(hgPackageReader.getPackages().getPackages()).thenReturn(createPackages()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldGetPackages() throws Exception { + MockHttpResponse response = get(); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + String responseString = response.getContentAsString(); + ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class); + + + JsonNode embedded = responseJson.get("_embedded"); + assertThat(embedded).isNotNull(); + + JsonNode packages = embedded.get("packages"); + assertThat(packages).isNotNull(); + assertThat(packages).hasSize(2); + + JsonNode package1 = packages.get(0); + assertThat(package1.get("_links")).isNull(); + + JsonNode hgConfigTemplate = package1.get("hgConfigTemplate"); + assertThat(hgConfigTemplate).isNotNull(); + assertThat(hgConfigTemplate.get("_links")).isNull(); + + assertThat(responseString).contains("\"_links\":{\"self\":{\"href\":\"/v2/config/hg/packages"); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetPackagesOnlyWhenAuthorized() throws Exception { + thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + + get(); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldInstallPackage() throws Exception { + String packgeId = "ourPackage"; + String url = "http://url"; + + setupPackageInstallation(packgeId, url); + when(advancedHttpClient.get(url).request().contentAsStream()) + .thenReturn(new ByteArrayInputStream("mockedFile".getBytes())); + + MockHttpResponse response = put(packgeId); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldHandleFailingInstallation() throws Exception { + String packgeId = "ourPackage"; + String url = "http://url"; + + setupPackageInstallation(packgeId, url); + when(advancedHttpClient.get(url).request().contentAsStream()) + .thenThrow(new IOException("mocked Exception")); + + MockHttpResponse response = put(packgeId); + assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldHandleMissingPackageId() throws Exception { + + MockHttpResponse response = put(null); + assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldInstallPackageOnlyWhenAuthorized() throws Exception { + thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + + put("don-t-care"); + } + + private List createPackages() { + return Arrays.asList(createPackage(), new HgPackage()); + } + + private MockHttpResponse get() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get(URI); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private MockHttpResponse put(String pckgId) throws URISyntaxException { + String packgeIdParam = ""; + if (pckgId != null) { + packgeIdParam = "/" + pckgId; + } + MockHttpRequest request = MockHttpRequest.put(URI + packgeIdParam); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private void setupResources() { + HgConfigPackageCollectionToDtoMapper mapper = + new HgConfigPackageCollectionToDtoMapper(hgConfigPackageToDtoMapper, uriInfoStore); + + HgConfigPackageResource hgConfigPackageResource = + new HgConfigPackageResource(hgPackageReader, advancedHttpClient, repositoryHandler, mapper); + + when(hgConfigPackageResourceProvider.get()).thenReturn(hgConfigPackageResource); + dispatcher.getRegistry().addSingletonResource( + new HgConfigResource(null, null, null, + hgConfigPackageResourceProvider, null, null)); + } + + private void setupPackageInstallation(String packgeId, String url) throws IOException { + when(hgPackage.getId()).thenReturn(packgeId); + when(hgPackageReader.getPackage(packgeId)).thenReturn(hgPackage); + when(repositoryHandler.getConfig()).thenReturn(new HgConfig()); + when(hgPackage.getHgConfigTemplate()).thenReturn(new HgConfig()); + when(hgPackage.getUrl()).thenReturn(url); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java new file mode 100644 index 0000000000..205096460a --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java @@ -0,0 +1,43 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.installer.HgPackage; + +import java.net.URI; + +import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage; +import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; + +@RunWith(MockitoJUnitRunner.class) +public class HgConfigPackageToDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private HgConfigPackageToDtoMapperImpl mapper; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + } + + @Test + public void shouldMapFields() { + HgPackage hgPackage = createPackage(); + + HgConfigPackageDto dto = mapper.map(hgPackage); + + assertEqualsPackage(dto); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java new file mode 100644 index 0000000000..4022d35f1c --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java @@ -0,0 +1,71 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.installer.HgPackage; +import sonia.scm.repository.HgConfig; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +class HgConfigTests { + + private HgConfigTests() { + } + + static HgConfig createConfiguration() { + HgConfig config = new HgConfig(); + config.setDisabled(true); + config.setRepositoryDirectory(new File("repository/directory")); + + config.setEncoding("ABC"); + config.setHgBinary("/etc/hg"); + config.setPythonBinary("/py"); + config.setPythonPath("/etc/"); + config.setShowRevisionInId(true); + config.setUseOptimizedBytecode(true); + + return config; + } + + static void assertEqualsConfiguration(HgConfigDto dto) { + assertTrue(dto.isDisabled()); + assertEquals("repository/directory", dto.getRepositoryDirectory().getPath()); + + assertEquals("ABC", dto.getEncoding()); + assertEquals("/etc/hg", dto.getHgBinary()); + assertEquals("/py", dto.getPythonBinary()); + assertEquals("/etc/", dto.getPythonPath()); + assertTrue(dto.isShowRevisionInId()); + assertTrue(dto.isUseOptimizedBytecode()); + } + + static HgPackage createPackage() { + HgPackage hgPackage= new HgPackage(); + hgPackage.setArch("arch"); + hgPackage.setId("1"); + hgPackage.setHgVersion("2"); + hgPackage.setPlatform("someOs"); + hgPackage.setPythonVersion("3"); + hgPackage.setSize(4); + hgPackage.setUrl("https://package"); + hgPackage.setHgConfigTemplate(createConfiguration()); + return hgPackage; + } + + static void assertEqualsPackage(HgConfigPackageDto dto) { + assertEquals("arch", dto.getArch()); + assertEquals("1", dto.getId()); + assertEquals("2", dto.getHgVersion()); + assertEquals("someOs", dto.getPlatform()); + assertEquals("3", dto.getPythonVersion()); + assertEquals(4, dto.getSize()); + assertEquals("https://package", dto.getUrl()); + + assertEqualsConfiguration(dto.getHgConfigTemplate()); + assertTrue(dto.getHgConfigTemplate().getLinks().isEmpty()); + + assertTrue(dto.getLinks().isEmpty()); + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java index 4c61874870..a12e95926c 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java @@ -14,14 +14,14 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; -import java.io.File; import java.net.URI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsConfiguration; +import static sonia.scm.api.v2.resources.HgConfigTests.createConfiguration; @RunWith(MockitoJUnitRunner.class) public class HgConfigToHgConfigDtoMapperTest { @@ -59,15 +59,7 @@ public class HgConfigToHgConfigDtoMapperTest { when(subject.isPermitted("configuration:write:hg")).thenReturn(true); HgConfigDto dto = mapper.map(config); - assertTrue(dto.isDisabled()); - assertEquals("repository/directory", dto.getRepositoryDirectory().getPath()); - - assertEquals("ABC", dto.getEncoding()); - assertEquals("/etc/hg", dto.getHgBinary()); - assertEquals("/py", dto.getPythonBinary()); - assertEquals("/etc/", dto.getPythonPath()); - assertTrue(dto.isShowRevisionInId()); - assertTrue(dto.isUseOptimizedBytecode()); + assertEqualsConfiguration(dto); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); @@ -83,20 +75,4 @@ public class HgConfigToHgConfigDtoMapperTest { assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); assertFalse(dto.getLinks().hasLink("update")); } - - private HgConfig createConfiguration() { - HgConfig config = new HgConfig(); - config.setDisabled(true); - config.setRepositoryDirectory(new File("repository/directory")); - - config.setEncoding("ABC"); - config.setHgBinary("/etc/hg"); - config.setPythonBinary("/py"); - config.setPythonPath("/etc/"); - config.setShowRevisionInId(true); - config.setUseOptimizedBytecode(true); - - return config; - } - } From 45e48e1834e6db08e16f6c21420b9d383f4b6fc0 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 17:00:50 +0200 Subject: [PATCH 39/45] Adds test for HgConfigInstallationsResource, its DTO and mappers. --- .../HgConfigInstallationsResource.java | 12 +- .../HgConfigInstallationsToDtoMapper.java | 21 ++-- .../HgConfigPackageCollectionToDtoMapper.java | 2 + .../HgConfigInstallationsResourceTest.java | 118 ++++++++++++++++++ .../HgConfigInstallationsToDtoMapperTest.java | 51 ++++++++ .../HgConfigPackageResourceTest.java | 4 - 6 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java index e1afd8c3b2..8842d07569 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsResource.java @@ -16,6 +16,8 @@ import javax.ws.rs.Produces; public class HgConfigInstallationsResource { + public static final String PATH_HG = "hg"; + public static final String PATH_PYTHON = "python"; private final HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper; @Inject @@ -27,7 +29,7 @@ public class HgConfigInstallationsResource { * Returns the hg installations. */ @GET - @Path("hg") + @Path(PATH_HG) @Produces(HgVndMediaType.INSTALLATIONS) @TypeHint(HalRepresentation.class) @StatusCodes({ @@ -40,14 +42,15 @@ public class HgConfigInstallationsResource { ConfigurationPermissions.read(HgConfig.PERMISSION).check(); - return hgConfigInstallationsToDtoMapper.map(HgInstallerFactory.createInstaller().getHgInstallations()); + return hgConfigInstallationsToDtoMapper.map( + HgInstallerFactory.createInstaller().getHgInstallations(), PATH_HG); } /** * Returns the python installations. */ @GET - @Path("python") + @Path(PATH_PYTHON) @Produces(HgVndMediaType.INSTALLATIONS) @TypeHint(HalRepresentation.class) @StatusCodes({ @@ -60,6 +63,7 @@ public class HgConfigInstallationsResource { ConfigurationPermissions.read(HgConfig.PERMISSION).check(); - return hgConfigInstallationsToDtoMapper.map(HgInstallerFactory.createInstaller().getPythonInstallations()); + return hgConfigInstallationsToDtoMapper.map( + HgInstallerFactory.createInstaller().getPythonInstallations(), PATH_PYTHON); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java index 767c05abbe..575166c007 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java @@ -1,21 +1,26 @@ package sonia.scm.api.v2.resources; -import de.otto.edison.hal.HalRepresentation; +import com.google.inject.Inject; -import javax.inject.Inject; import java.util.List; import static de.otto.edison.hal.Links.linkingTo; public class HgConfigInstallationsToDtoMapper { - @Inject private UriInfoStore uriInfoStore; - public HalRepresentation map(List installations) { - return new HgConfigInstallationsDto(linkingTo().self(createSelfLink()).build(), installations); + private UriInfoStore uriInfoStore; + + @Inject + public HgConfigInstallationsToDtoMapper(UriInfoStore uriInfoStore, String path) { + this.uriInfoStore = uriInfoStore; } - private String createSelfLink() { - LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigInstallationsResource.class); - return linkBuilder.method("get").parameters().href(); + public HgConfigInstallationsDto map(List installations, String path) { + return new HgConfigInstallationsDto(linkingTo().self(createSelfLink(path)).build(), installations); + } + + private String createSelfLink(String path) { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); + return linkBuilder.method("getInstallationsResource").parameters().href() + '/' + path; } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java index 5e69c8b199..46f4526aa4 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java @@ -4,6 +4,8 @@ import sonia.scm.installer.HgPackage; import javax.inject.Inject; +// TODO could this be simplified similar to HgConfigInstallationsToDtoMapper? +// That is, do we really need the packages as _embedded list? public class HgConfigPackageCollectionToDtoMapper extends CollectionToDtoMapper { static final String COLLECTION_NAME = "packages"; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java new file mode 100644 index 0000000000..f2fe4165f9 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java @@ -0,0 +1,118 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.inject.Provider; +import javax.servlet.http.HttpServletResponse; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@SubjectAware( + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" +) +@RunWith(MockitoJUnitRunner.class) +public class HgConfigInstallationsResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = URI.create("/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private HgConfigInstallationsToDtoMapper mapper; + + @Mock + private Provider resourceProvider; + + + @Before + public void prepareEnvironment() { + HgConfigInstallationsResource resource = new HgConfigInstallationsResource(mapper); + + when(resourceProvider.get()).thenReturn(resource); + dispatcher.getRegistry().addSingletonResource( + new HgConfigResource(null, null, null, null, + null, resourceProvider)); + + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldGetHgInstallations() throws Exception { + MockHttpResponse response = get("hg"); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + String contentAsString = response.getContentAsString(); + assertThat(contentAsString).contains("{\"paths\":["); + assertThat(contentAsString).contains("hg"); + assertThat(contentAsString).doesNotContain("python"); + + assertThat(contentAsString).contains("\"self\":{\"href\":\"/v2/config/hg/installations/hg"); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetHgInstallationsOnlyWhenAuthorized() throws Exception { + thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + + get("hg"); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldGetPythonInstallations() throws Exception { + MockHttpResponse response = get("python"); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + + String contentAsString = response.getContentAsString(); + assertThat(contentAsString).contains("{\"paths\":["); + assertThat(contentAsString).contains("python"); + + assertThat(contentAsString).contains("\"self\":{\"href\":\"/v2/config/hg/installations/python"); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetPythonInstallationsOnlyWhenAuthorized() throws Exception { + thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + + get("python"); + } + + private MockHttpResponse get(String path) throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + path); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java new file mode 100644 index 0000000000..34048f80b2 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java @@ -0,0 +1,51 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class HgConfigInstallationsToDtoMapperTest { + + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private HgConfigInstallationsToDtoMapper mapper; + + private URI expectedBaseUri; + + private String expectedPath = "path"; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + expectedPath); + } + + @Test + public void shouldMapFields() { + List installations = Arrays.asList("/hg", "/bin/hg"); + + HgConfigInstallationsDto dto = mapper.map(installations, expectedPath); + + assertThat(dto.getPaths()).isEqualTo(installations); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index 0bb7d3e048..5c0fc65e26 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -13,7 +13,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -53,9 +52,6 @@ public class HgConfigPackageResourceTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @Rule - public TemporaryFolder folder= new TemporaryFolder(); - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); private final URI baseUri = java.net.URI.create("/"); From 730c2ae358975ffbd70fc9fd8e81fc0e49ba1625 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 17:39:05 +0200 Subject: [PATCH 40/45] Adds test for HgConfigAutoConfigurationResource, its DTO and mappers. --- .../HgConfigAutoConfigurationResource.java | 16 ++- .../v2/resources/HgConfigPackageResource.java | 2 +- .../api/v2/resources/HgConfigResource.java | 4 +- ...HgConfigAutoConfigurationResourceTest.java | 125 ++++++++++++++++++ 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java index 2198262275..b265f2929d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResource.java @@ -7,7 +7,9 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.web.HgVndMediaType; +import javax.ws.rs.Consumes; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.core.Response; @@ -15,9 +17,12 @@ import javax.ws.rs.core.Response; public class HgConfigAutoConfigurationResource { private final HgRepositoryHandler repositoryHandler; + private final HgConfigDtoToHgConfigMapper dtoToConfigMapper; @Inject - public HgConfigAutoConfigurationResource(HgRepositoryHandler repositoryHandler) { + public HgConfigAutoConfigurationResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, + HgRepositoryHandler repositoryHandler) { + this.dtoToConfigMapper = dtoToConfigMapper; this.repositoryHandler = repositoryHandler; } @@ -44,6 +49,7 @@ public class HgConfigAutoConfigurationResource { */ @PUT @Path("") + @Consumes(HgVndMediaType.CONFIG) @StatusCodes({ @ResponseCode(code = 204, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @@ -52,11 +58,13 @@ public class HgConfigAutoConfigurationResource { }) @TypeHint(TypeHint.NO_CONTENT.class) public Response autoConfiguration(HgConfigDto configDto) { - HgConfig config = repositoryHandler.getConfig(); - if (config == null) { + HgConfig config; + + if (configDto != null) { + config = dtoToConfigMapper.map(configDto); + } else { config = new HgConfig(); - repositoryHandler.setConfig(config); } ConfigurationPermissions.write(config).check(); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java index a2fca37ff2..7e3c0e9a66 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java @@ -80,7 +80,7 @@ public class HgConfigPackageResource { HgPackage pkg = pkgReader.getPackage(pkgId); - // First path parm cannot be null (leaving it results in 405) + // First path param cannot be null (leaving it results in 405) if (HgInstallerFactory.createInstaller() .installPackage(client, handler, SCMContext.getContext().getBaseDirectory(), pkg)) { response = Response.noContent().build(); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index f98300251f..e6a8f01238 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -60,6 +60,8 @@ public class HgConfigResource { }) public Response get() { + ConfigurationPermissions.read(HgConfig.PERMISSION).check(); + HgConfig config = repositoryHandler.getConfig(); if (config == null) { @@ -67,8 +69,6 @@ public class HgConfigResource { repositoryHandler.setConfig(config); } - ConfigurationPermissions.read(config).check(); - return Response.ok(configToDtoMapper.map(config)).build(); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java new file mode 100644 index 0000000000..0c766eb4e6 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java @@ -0,0 +1,125 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.web.HgVndMediaType; + +import javax.inject.Provider; +import javax.servlet.http.HttpServletResponse; +import java.net.URISyntaxException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SubjectAware( + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" +) +@RunWith(MockitoJUnitRunner.class) +public class HgConfigAutoConfigurationResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + @InjectMocks + private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper; + + @Mock + private HgRepositoryHandler repositoryHandler; + + @Mock + private Provider resourceProvider; + + @Before + public void prepareEnvironment() { + HgConfigAutoConfigurationResource resource = + new HgConfigAutoConfigurationResource(dtoToConfigMapper, repositoryHandler); + + when(resourceProvider.get()).thenReturn(resource); + dispatcher.getRegistry().addSingletonResource( + new HgConfigResource(null, null, null, null, + resourceProvider, null)); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldSetDefaultConfigAndInstallHg() throws Exception { + MockHttpResponse response = put(null); + + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + + HgConfig actualConfig = captureConfig(); + assertFalse(actualConfig.isDisabled()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldSetDefaultConfigAndInstallHgOnlyWhenAuthorized() throws Exception { + thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + + put(null); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldUpdateConfigAndInstallHg() throws Exception { + MockHttpResponse response = put("{\"disabled\":true}"); + + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + + HgConfig actualConfig = captureConfig(); + assertTrue(actualConfig.isDisabled()); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldUpdateConfigAndInstallHgOnlyWhenAuthorized() throws Exception { + thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + + put("{\"disabled\":true}"); + } + + private MockHttpResponse put(String content) throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/auto-configuration"); + + if (content != null) { + request + .contentType(HgVndMediaType.CONFIG) + .content(content.getBytes()); + } + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + return response; + } + + private HgConfig captureConfig() { + ArgumentCaptor configCaptor = ArgumentCaptor.forClass(HgConfig.class); + verify(repositoryHandler).doAutoConfiguration(configCaptor.capture()); + return configCaptor.getValue(); + } + +} From 869821f6db9319bdc375ccaff10a260274ca1662 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 6 Aug 2018 17:47:46 +0200 Subject: [PATCH 41/45] Fixes some SQ Issues regarding DTO add(Links) methods. --- .../main/java/sonia/scm/api/v2/resources/GitConfigDto.java | 1 + .../main/java/sonia/scm/api/v2/resources/HgConfigDto.java | 1 + .../java/sonia/scm/api/v2/resources/HgConfigPackageDto.java | 6 ------ .../main/java/sonia/scm/api/v2/resources/SvnConfigDto.java | 1 + .../src/main/java/sonia/scm/api/v2/resources/GroupDto.java | 1 + 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java index 8ecea58a19..19a014dcdf 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java @@ -19,6 +19,7 @@ public class GitConfigDto extends HalRepresentation { private String gcExpression; @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package protected HalRepresentation add(Links links) { return super.add(links); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java index 889c156f63..9fefc05ca4 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigDto.java @@ -24,6 +24,7 @@ public class HgConfigDto extends HalRepresentation { private boolean showRevisionInId; @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package protected HalRepresentation add(Links links) { return super.add(links); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java index bd73b7d76c..8e22d1026d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -19,9 +18,4 @@ public class HgConfigPackageDto extends HalRepresentation { private String pythonVersion; private long size; private String url; - - @Override - protected HalRepresentation add(Links links) { - return super.add(links); - } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java index d0a36f1a85..548944a49c 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigDto.java @@ -21,6 +21,7 @@ public class SvnConfigDto extends HalRepresentation { private Compatibility compatibility; @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package protected HalRepresentation add(Links links) { return super.add(links); } 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 3944be81b8..cb05da6568 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 @@ -24,6 +24,7 @@ public class GroupDto extends HalRepresentation { private List members; @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package protected HalRepresentation add(Links links) { return super.add(links); } From adde70f090ee14278f3c87258bab9e992d4c183b Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 7 Aug 2018 10:44:18 +0200 Subject: [PATCH 42/45] Fixes NPE in HgConfigPackageResource for non-existing packages. --- .../api/v2/resources/HgConfigPackageResource.java | 13 ++++++++----- .../v2/resources/HgConfigPackageResourceTest.java | 9 +++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java index 7e3c0e9a66..124c2dcb61 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java @@ -80,12 +80,15 @@ public class HgConfigPackageResource { HgPackage pkg = pkgReader.getPackage(pkgId); - // First path param cannot be null (leaving it results in 405) - if (HgInstallerFactory.createInstaller() - .installPackage(client, handler, SCMContext.getContext().getBaseDirectory(), pkg)) { - response = Response.noContent().build(); + if (pkg != null) { + if (HgInstallerFactory.createInstaller() + .installPackage(client, handler, SCMContext.getContext().getBaseDirectory(), pkg)) { + response = Response.noContent().build(); + } else { + response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } } else { - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + response = Response.status(Response.Status.NOT_FOUND).build(); } return response; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index 5c0fc65e26..e91662df3f 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -152,10 +152,11 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldHandleMissingPackageId() throws Exception { - - MockHttpResponse response = put(null); - assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus()); + public void shouldHandlePackagesThatAreNotFound() throws Exception { + String packageId = "this-package-does-not-ex"; + when(hgPackageReader.getPackage(packageId)).thenReturn(null); + MockHttpResponse response = put(packageId); + assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus()); } @Test From 45822c44cbf9b6501e2680a3a6d3227e55ced8d7 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 7 Aug 2018 16:47:03 +0200 Subject: [PATCH 43/45] Deliver HgConfigPackages no longer as embedded. They don't have an own identity in terms of REST and its much simpler to return a simple list instead of an _embedded list. Also gets rid of one HgConfigPackageToDtoMapper. --- .../resources/HgConfigInstallationsDto.java | 5 +- .../HgConfigInstallationsToDtoMapper.java | 4 +- .../HgConfigPackageCollectionToDtoMapper.java | 25 -------- .../api/v2/resources/HgConfigPackageDto.java | 21 ------- .../resources/HgConfigPackageToDtoMapper.java | 20 ------- .../api/v2/resources/HgConfigPackagesDto.java | 38 ++++++++++++ .../HgConfigPackagesToDtoMapper.java | 59 +++++++++++++++++++ .../java/sonia/scm/web/HgServletModule.java | 6 +- .../HgConfigPackageResourceTest.java | 15 ++--- .../HgConfigPackageToDtoMapperTest.java | 43 -------------- ...a => HgConfigPackagesToDtoMapperTest.java} | 28 ++++----- .../scm/api/v2/resources/HgConfigTests.java | 4 +- 12 files changed, 118 insertions(+), 150 deletions(-) delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java delete mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java rename scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/{HgConfigPackageCollectionToDtoMapperTest.java => HgConfigPackagesToDtoMapperTest.java} (58%) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java index be97e2972b..b60f7f5460 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsDto.java @@ -3,20 +3,19 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; -@NoArgsConstructor @Getter @Setter public class HgConfigInstallationsDto extends HalRepresentation { + private List paths; + public HgConfigInstallationsDto(Links links, List paths) { super(links); this.paths = paths; } - private List paths; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java index 575166c007..d2f4aecf7e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapper.java @@ -1,7 +1,7 @@ package sonia.scm.api.v2.resources; -import com.google.inject.Inject; +import javax.inject.Inject; import java.util.List; import static de.otto.edison.hal.Links.linkingTo; @@ -11,7 +11,7 @@ public class HgConfigInstallationsToDtoMapper { private UriInfoStore uriInfoStore; @Inject - public HgConfigInstallationsToDtoMapper(UriInfoStore uriInfoStore, String path) { + public HgConfigInstallationsToDtoMapper(UriInfoStore uriInfoStore) { this.uriInfoStore = uriInfoStore; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java deleted file mode 100644 index 46f4526aa4..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package sonia.scm.api.v2.resources; - -import sonia.scm.installer.HgPackage; - -import javax.inject.Inject; - -// TODO could this be simplified similar to HgConfigInstallationsToDtoMapper? -// That is, do we really need the packages as _embedded list? -public class HgConfigPackageCollectionToDtoMapper extends CollectionToDtoMapper { - - static final String COLLECTION_NAME = "packages"; - private UriInfoStore uriInfoStore; - - @Inject - public HgConfigPackageCollectionToDtoMapper(HgConfigPackageToDtoMapper mapper, UriInfoStore uriInfoStore) { - super(COLLECTION_NAME, mapper); - this.uriInfoStore = uriInfoStore; - } - - @Override - protected String createSelfLink() { - LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); - return linkBuilder.method("getPackagesResource").parameters().href(); - } -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java deleted file mode 100644 index 8e22d1026d..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package sonia.scm.api.v2.resources; - -import de.otto.edison.hal.HalRepresentation; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@NoArgsConstructor -@Getter -@Setter -public class HgConfigPackageDto extends HalRepresentation { - - private String arch; - private HgConfigDto hgConfigTemplate; - private String hgVersion; - private String id; - private String platform; - private String pythonVersion; - private long size; - private String url; -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java deleted file mode 100644 index 31671e3e14..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package sonia.scm.api.v2.resources; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import sonia.scm.installer.HgPackage; - -import javax.inject.Inject; - -@Mapper -public abstract class HgConfigPackageToDtoMapper extends BaseMapper { - @Inject - private UriInfoStore uriInfoStore; - - @Override - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - @Mapping(target = "hgConfigTemplate.attributes", ignore = true) // Also not for nested DTOs - public abstract HgConfigPackageDto map(HgPackage modelObject); - - // Don't add links because ConfigPackages don't have their own ressource -} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java new file mode 100644 index 0000000000..959df12b61 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesDto.java @@ -0,0 +1,38 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@NoArgsConstructor +@Getter +@Setter +public class HgConfigPackagesDto extends HalRepresentation { + + private List packages; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + + @NoArgsConstructor + @Getter + @Setter + public static class HgConfigPackageDto { + + private String arch; + private HgConfigDto hgConfigTemplate; + private String hgVersion; + private String id; + private String platform; + private String pythonVersion; + private long size; + private String url; + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java new file mode 100644 index 0000000000..67d7e58dff --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapper.java @@ -0,0 +1,59 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import lombok.Getter; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.installer.HgPackage; +import sonia.scm.installer.HgPackages; + +import javax.inject.Inject; +import java.util.List; + +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class HgConfigPackagesToDtoMapper { + + @Inject + private UriInfoStore uriInfoStore; + + public HgConfigPackagesDto map(HgPackages hgpackages) { + return map(new HgPackagesNonIterable(hgpackages)); + } + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + /* Favor warning "Unmapped target property: "attributes", to packages[].hgConfigTemplate" + Over error "Unknown property "packages[].hgConfigTemplate.attributes" + @Mapping(target = "packages[].hgConfigTemplate.attributes", ignore = true) // Also not for nested DTOs + */ + protected abstract HgConfigPackagesDto map(HgPackagesNonIterable hgPackagesNonIterable); + + @AfterMapping + void appendLinks(@MappingTarget HgConfigPackagesDto target) { + Links.Builder linksBuilder = linkingTo().self(createSelfLink()); + target.add(linksBuilder.build()); + } + + private String createSelfLink() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class); + return linkBuilder.method("getPackagesResource").parameters().href(); + } + + /** + * Unfortunately, HgPackages is iterable, HgConfigPackagesDto does not need to be iterable and MapStruct refuses to + * map an iterable to a non-iterable. So use this little non-iterable "proxy". + */ + @Getter + static class HgPackagesNonIterable { + private List packages; + + HgPackagesNonIterable(HgPackages hgPackages) { + this.packages = hgPackages.getPackages(); + } + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index dd1fbd4748..357995483d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -39,8 +39,7 @@ import com.google.inject.servlet.ServletModule; import org.mapstruct.factory.Mappers; import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper; import sonia.scm.api.v2.resources.HgConfigInstallationsToDtoMapper; -import sonia.scm.api.v2.resources.HgConfigPackageCollectionToDtoMapper; -import sonia.scm.api.v2.resources.HgConfigPackageToDtoMapper; +import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper; import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper; import sonia.scm.installer.HgPackageReader; import sonia.scm.plugin.Extension; @@ -77,8 +76,7 @@ public class HgServletModule extends ServletModule bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass()); bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass()); - bind(HgConfigPackageToDtoMapper.class).to(Mappers.getMapper(HgConfigPackageToDtoMapper.class).getClass()); - bind(HgConfigPackageCollectionToDtoMapper.class); + bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass()); bind(HgConfigInstallationsToDtoMapper.class); // bind servlets diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index e91662df3f..a2ae6739eb 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -56,12 +56,12 @@ public class HgConfigPackageResourceTest { private final URI baseUri = java.net.URI.create("/"); - @InjectMocks - private HgConfigPackageToDtoMapperImpl hgConfigPackageToDtoMapper; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) private UriInfoStore uriInfoStore; + @InjectMocks + private HgConfigPackagesToDtoMapperImpl mapper; + @Mock private HgRepositoryHandler repositoryHandler; @@ -96,11 +96,7 @@ public class HgConfigPackageResourceTest { String responseString = response.getContentAsString(); ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class); - - JsonNode embedded = responseJson.get("_embedded"); - assertThat(embedded).isNotNull(); - - JsonNode packages = embedded.get("packages"); + JsonNode packages = responseJson.get("packages"); assertThat(packages).isNotNull(); assertThat(packages).hasSize(2); @@ -191,9 +187,6 @@ public class HgConfigPackageResourceTest { } private void setupResources() { - HgConfigPackageCollectionToDtoMapper mapper = - new HgConfigPackageCollectionToDtoMapper(hgConfigPackageToDtoMapper, uriInfoStore); - HgConfigPackageResource hgConfigPackageResource = new HgConfigPackageResource(hgPackageReader, advancedHttpClient, repositoryHandler, mapper); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java deleted file mode 100644 index 205096460a..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageToDtoMapperTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package sonia.scm.api.v2.resources; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import sonia.scm.installer.HgPackage; - -import java.net.URI; - -import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage; -import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; - -@RunWith(MockitoJUnitRunner.class) -public class HgConfigPackageToDtoMapperTest { - - private URI baseUri = URI.create("http://example.com/base/"); - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private UriInfoStore uriInfoStore; - - @InjectMocks - private HgConfigPackageToDtoMapperImpl mapper; - - @Before - public void init() { - when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); - } - - @Test - public void shouldMapFields() { - HgPackage hgPackage = createPackage(); - - HgConfigPackageDto dto = mapper.map(hgPackage); - - assertEqualsPackage(dto); - } - -} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java similarity index 58% rename from scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java rename to scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java index 27cd3c73c3..671d9fb7e1 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageCollectionToDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java @@ -9,6 +9,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.installer.HgPackage; +import sonia.scm.installer.HgPackages; import java.net.URI; import java.util.Arrays; @@ -19,12 +20,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.HgConfigPackageCollectionToDtoMapper.COLLECTION_NAME; import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage; import static sonia.scm.api.v2.resources.HgConfigTests.createPackage; @RunWith(MockitoJUnitRunner.class) -public class HgConfigPackageCollectionToDtoMapperTest { +public class HgConfigPackagesToDtoMapperTest { private URI baseUri = URI.create("http://example.com/base/"); @@ -32,9 +32,7 @@ public class HgConfigPackageCollectionToDtoMapperTest { private UriInfoStore uriInfoStore; @InjectMocks - private HgConfigPackageToDtoMapperImpl hgConfigPackageToDtoMapper; - - private HgConfigPackageCollectionToDtoMapper mapper; + private HgConfigPackagesToDtoMapperImpl mapper; private URI expectedBaseUri; @@ -42,35 +40,29 @@ public class HgConfigPackageCollectionToDtoMapperTest { public void init() { when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/packages"); - mapper = new HgConfigPackageCollectionToDtoMapper(hgConfigPackageToDtoMapper, uriInfoStore); } @Test public void shouldMapFields() { - Collection hgPackages = createPackages(); + HgPackages hgPackages = new HgPackages(); + hgPackages.setPackages(createPackages()); - HalRepresentation dto = mapper.map(hgPackages); + HgConfigPackagesDto dto = mapper.map(hgPackages); - List itemsBy = dto.getEmbedded().getItemsBy(COLLECTION_NAME); - assertThat(itemsBy).hasSize(2); + assertThat(dto.getPackages()).hasSize(2); - HgConfigPackageDto hgPackageDto1 = assertAndGetAsDto(itemsBy.get(0)); + HgConfigPackagesDto.HgConfigPackageDto hgPackageDto1 = dto.getPackages().get(0); assertEqualsPackage(hgPackageDto1); - HgConfigPackageDto hgPackageDto2 = assertAndGetAsDto(itemsBy.get(1)); - assertTrue(hgPackageDto2.getLinks().isEmpty()); + HgConfigPackagesDto.HgConfigPackageDto hgPackageDto2 = dto.getPackages().get(1); // Just verify a random field assertThat(hgPackageDto2.getId()).isNull(); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); } - private HgConfigPackageDto assertAndGetAsDto(HalRepresentation halRepresentation) { - assertThat(halRepresentation).isInstanceOf(HgConfigPackageDto.class); - return (HgConfigPackageDto) halRepresentation; - } - private Collection createPackages() { + private List createPackages() { return Arrays.asList(createPackage(), new HgPackage()); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java index 4022d35f1c..4167321344 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java @@ -53,7 +53,7 @@ class HgConfigTests { return hgPackage; } - static void assertEqualsPackage(HgConfigPackageDto dto) { + static void assertEqualsPackage(HgConfigPackagesDto.HgConfigPackageDto dto) { assertEquals("arch", dto.getArch()); assertEquals("1", dto.getId()); assertEquals("2", dto.getHgVersion()); @@ -64,8 +64,6 @@ class HgConfigTests { assertEqualsConfiguration(dto.getHgConfigTemplate()); assertTrue(dto.getHgConfigTemplate().getLinks().isEmpty()); - - assertTrue(dto.getLinks().isEmpty()); } } From 15da105683dcb91ce5c25c1d5acc117966a2f7a5 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 7 Aug 2018 17:23:52 +0200 Subject: [PATCH 44/45] Commits missing changes for HgConfigPackagesToDtoMapper --- .../scm/api/v2/resources/HgConfigPackageResource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java index 124c2dcb61..88a7de7ea0 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigPackageResource.java @@ -27,15 +27,15 @@ public class HgConfigPackageResource { private final HgPackageReader pkgReader; private final AdvancedHttpClient client; private final HgRepositoryHandler handler; - private final HgConfigPackageCollectionToDtoMapper configPackageCollectionToDtoMapper; + private final HgConfigPackagesToDtoMapper configPackageCollectionToDtoMapper; @Inject public HgConfigPackageResource(HgPackageReader pkgReader, AdvancedHttpClient client, HgRepositoryHandler handler, - HgConfigPackageCollectionToDtoMapper configPackageCollectionToDtoMapper) { + HgConfigPackagesToDtoMapper hgConfigPackagesToDtoMapper) { this.pkgReader = pkgReader; this.client = client; this.handler = handler; - this.configPackageCollectionToDtoMapper = configPackageCollectionToDtoMapper; + this.configPackageCollectionToDtoMapper = hgConfigPackagesToDtoMapper; } /** @@ -55,7 +55,7 @@ public class HgConfigPackageResource { ConfigurationPermissions.read(HgConfig.PERMISSION).check(); - return configPackageCollectionToDtoMapper.map(pkgReader.getPackages().getPackages()); + return configPackageCollectionToDtoMapper.map(pkgReader.getPackages()); } /** From 390ba29beed56fc44ba80ef27e8f1bedea15eef4 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 8 Aug 2018 15:14:39 +0200 Subject: [PATCH 45/45] Renamed tests --- .../sonia/scm/api/v2/resources/GitConfigResourceTest.java | 4 ++-- .../resources/HgConfigAutoConfigurationResourceTest.java | 8 +++----- .../v2/resources/HgConfigInstallationsResourceTest.java | 4 ++-- .../scm/api/v2/resources/HgConfigPackageResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/HgConfigResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/SvnConfigResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/ConfigResourceTest.java | 8 +++----- 7 files changed, 16 insertions(+), 20 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index feed89aa8f..42790ea7a4 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -110,7 +110,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { thrown.expectMessage("Subject does not have permission [configuration:read:git]"); get(); @@ -125,7 +125,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException { thrown.expectMessage("Subject does not have permission [configuration:write:git]"); put(); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java index 0c766eb4e6..4b66444bbe 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java @@ -23,9 +23,7 @@ import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; import java.net.URISyntaxException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -77,7 +75,7 @@ public class HgConfigAutoConfigurationResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldSetDefaultConfigAndInstallHgOnlyWhenAuthorized() throws Exception { + public void shouldNotSetDefaultConfigAndInstallHgWhenNotAuthorized() throws Exception { thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); put(null); @@ -96,7 +94,7 @@ public class HgConfigAutoConfigurationResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldUpdateConfigAndInstallHgOnlyWhenAuthorized() throws Exception { + public void shouldNotUpdateConfigAndInstallHgWhenNotAuthorized() throws Exception { thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); put("{\"disabled\":true}"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java index f2fe4165f9..540a5b6757 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java @@ -81,7 +81,7 @@ public class HgConfigInstallationsResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetHgInstallationsOnlyWhenAuthorized() throws Exception { + public void shouldNotGetHgInstallationsWhenNotAuthorized() throws Exception { thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); get("hg"); @@ -103,7 +103,7 @@ public class HgConfigInstallationsResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetPythonInstallationsOnlyWhenAuthorized() throws Exception { + public void shouldNotGetPythonInstallationsWhenNotAuthorized() throws Exception { thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); get("python"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index a2ae6739eb..044897ad80 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -112,7 +112,7 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetPackagesOnlyWhenAuthorized() throws Exception { + public void shouldNotGetPackagesWhenNotAuthorized() throws Exception { thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); get(); @@ -157,7 +157,7 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldInstallPackageOnlyWhenAuthorized() throws Exception { + public void shouldNotInstallPackageWhenNotAuthorized() throws Exception { thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); put("don-t-care"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index c655e8433a..11a0fb55f9 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -121,7 +121,7 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); get(); @@ -136,7 +136,7 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException { + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); put(); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java index 6c0601f977..de4d654910 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java @@ -109,7 +109,7 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { thrown.expectMessage("Subject does not have permission [configuration:read:svn]"); get(); @@ -124,7 +124,7 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException { + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { thrown.expectMessage("Subject does not have permission [configuration:write:svn]"); put(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 2a8a44b4d1..ff97ca332b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -21,9 +21,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.MockitoAnnotations.initMocks; @SubjectAware( @@ -72,7 +70,7 @@ public class ConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); @@ -104,7 +102,7 @@ public class ConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException { URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); byte[] configJson = Resources.toByteArray(url); MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)