diff --git a/pom.xml b/pom.xml index af38d5175a..788abaca36 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,7 @@ scm-ui scm-webapp scm-server + scm-it diff --git a/scm-it/pom.xml b/scm-it/pom.xml new file mode 100644 index 0000000000..ab68517a9d --- /dev/null +++ b/scm-it/pom.xml @@ -0,0 +1,173 @@ + + + + 4.0.0 + + + sonia.scm + scm + 2.0.0-SNAPSHOT + + + sonia.scm + scm-it + jar + 2.0.0-SNAPSHOT + scm-it + + + + sonia.scm + scm-core + 2.0.0-SNAPSHOT + + + io.rest-assured + rest-assured + 3.1.0 + test + + + + + + + + com.mycila.maven-license-plugin + maven-license-plugin + 1.9.0 + +
http://download.scm-manager.org/licenses/mvn-license.txt
+ + src/** + **/test/** + + + target/** + .hg/** + src/main/webapp/resources/extjs/** + src/main/webapp/resources/syntaxhighlighter/** + src/main/webapp/resources/moment/** + **/*.mustache + + true +
+
+ + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.maven.version} + + 8005 + STOP + + + scm.home + ${scm.home} + + + scm.stage + ${scm.stage} + + + java.awt.headless + true + + + + /scm + + ${project.basedir}/src/main/conf/jetty.xml + 0 + + +
+ + scm-it +
+ + + + + + + + it + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.12 + + + sonia/scm/it/*ITCase.java + + + + + integration-test + + integration-test + + + + verify + + verify + + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.maven.version} + + 8085 + STOP + + + scm.home + target/scm-it + + + scm.stage + ${scm.stage} + + + ${project.basedir}/src/main/conf/jetty.xml + ${project.basedir}/../scm-webapp/target/scm-webapp.war + 0 + true + + + + start-jetty + pre-integration-test + + deploy-war + + + + stop-jetty + post-integration-test + + stop + + + + + + + + + + + +
+ diff --git a/scm-it/src/main/conf/jetty.xml b/scm-it/src/main/conf/jetty.xml new file mode 100644 index 0000000000..ec7ac555c8 --- /dev/null +++ b/scm-it/src/main/conf/jetty.xml @@ -0,0 +1,72 @@ + + + + + + + + + 16384 + 16384 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scm-it/src/test/java/sonia/scm/it/RegExMatcher.java b/scm-it/src/test/java/sonia/scm/it/RegExMatcher.java new file mode 100644 index 0000000000..e5dc7931d3 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/RegExMatcher.java @@ -0,0 +1,29 @@ +package sonia.scm.it; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.regex.Pattern; + +class RegExMatcher extends BaseMatcher { + public static Matcher matchesPattern(String pattern) { + return new RegExMatcher(pattern); + } + + private final String pattern; + + private RegExMatcher(String pattern) { + this.pattern = pattern; + } + + @Override + public void describeTo(Description description) { + description.appendText("matching to regex pattern \"" + pattern + "\""); + } + + @Override + public boolean matches(Object o) { + return Pattern.compile(pattern).matcher(o.toString()).matches(); + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java new file mode 100644 index 0000000000..6e309e1ba6 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + */ + + +package sonia.scm.it; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import sonia.scm.util.IOUtil; +import sonia.scm.web.VndMediaType; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static sonia.scm.it.RegExMatcher.matchesPattern; +import static sonia.scm.it.RestUtil.createResourceUrl; +import static sonia.scm.it.RestUtil.given; +import static sonia.scm.it.RestUtil.readJson; + +@RunWith(Parameterized.class) +public class RepositoriesITCase { + + private final String repositoryType; + + public RepositoriesITCase(String repositoryType) { + this.repositoryType = repositoryType; + } + + @Parameters(name = "{0}") + public static Collection createParameters() { + Collection params = new ArrayList<>(); + + params.add(new String[]{"git"}); + params.add(new String[]{"svn"}); + + if (IOUtil.search("hg") != null) { + params.add(new String[]{"hg"}); + } + + return params; + } + + @After + public void cleanup() { + TestData.cleanup(); + } + + @Test + public void shouldCreateSuccessfully() throws IOException { + String repositoryUrl = createRepository(); + + given(VndMediaType.REPOSITORY) + + .when() + .get(repositoryUrl) + + .then() + .statusCode(HttpStatus.SC_OK) + .body( + "name", equalTo("HeartOfGold-" + repositoryType), + "type", equalTo(repositoryType), + "creationDate", matchesPattern("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+Z"), + "lastModified", is(nullValue()), + "_links.self.href", equalTo(repositoryUrl) + ); + } + + @Test + public void shouldDeleteSuccessfully() throws IOException { + String repositoryUrl = createRepository(); + + given(VndMediaType.REPOSITORY) + + .when() + .delete(repositoryUrl) + + .then() + .statusCode(HttpStatus.SC_NO_CONTENT); + + given(VndMediaType.REPOSITORY) + + .when() + .get(repositoryUrl) + + .then() + .statusCode(HttpStatus.SC_NOT_FOUND); + } + + @Test + public void shouldRejectMultipleCreations() throws IOException { + String repositoryJson = readJson("repository-" + repositoryType + ".json"); + createRepository(); + + given(VndMediaType.REPOSITORY) + .body(repositoryJson) + + .when() + .post(createResourceUrl("repositories")) + + .then() + .statusCode(HttpStatus.SC_CONFLICT); + } + + private String createRepository() throws IOException { + return given(VndMediaType.REPOSITORY) + .body(readJson("repository-" + repositoryType + ".json")) + + .when() + .post(createResourceUrl("repositories")) + + .then() + .statusCode(HttpStatus.SC_CREATED) + .extract() + .header("location"); + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/RestUtil.java b/scm-it/src/test/java/sonia/scm/it/RestUtil.java new file mode 100644 index 0000000000..a902c8985c --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/RestUtil.java @@ -0,0 +1,68 @@ +package sonia.scm.it; + +import com.google.common.io.Resources; +import io.restassured.RestAssured; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; + +import static java.util.Arrays.asList; + +public class RestUtil { + + public static final String BASE_URL = "http://localhost:8081/scm/"; + public static final String REST_BASE_URL = BASE_URL.concat("api/rest/v2/"); + + public static Response lastResponse; + + public static URI createResourceUrl(String url) + { + return URI.create(REST_BASE_URL).resolve(url); + } + + public static String readJson(String jsonFileName) throws IOException { + URL url = Resources.getResource(jsonFileName); + return Resources.toString(url, Charset.forName("UTF-8")); + } + + public static RequestSpecification given(String mediaType) { + RequestSpecification requestSpecification = RestAssured.given() + .contentType(mediaType) + .accept(mediaType) + .auth().preemptive().basic("scmadmin", "scmadmin"); + return wrapRequestSpecification(requestSpecification); + } + + private static RequestSpecification wrapRequestSpecification(RequestSpecification requestSpecification) { + return (RequestSpecification) Proxy.newProxyInstance(RestUtil.class.getClassLoader(), new Class[]{RequestSpecification.class}, new RequestSpecificationWrapper(requestSpecification)); + } + + private static class RequestSpecificationWrapper implements InvocationHandler { + + private final RequestSpecification delegate; + + private RequestSpecificationWrapper(RequestSpecification delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { + if (asList("get", "put", "post", "delete").contains(method.getName())) { + lastResponse = (Response) method.invoke(delegate, args); + return lastResponse; + } else if (method.getReturnType().equals(RequestSpecification.class)) { + return wrapRequestSpecification((RequestSpecification) method.invoke(delegate, args)); + } else { + return method.invoke(delegate, args); + } + } + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/TestData.java b/scm-it/src/test/java/sonia/scm/it/TestData.java new file mode 100644 index 0000000000..4127b74b9d --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/TestData.java @@ -0,0 +1,74 @@ +package sonia.scm.it; + +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.web.VndMediaType; + +import java.util.List; + +import static java.util.Arrays.asList; +import static sonia.scm.it.RestUtil.createResourceUrl; +import static sonia.scm.it.RestUtil.given; + +public class TestData { + + private static final Logger LOG = LoggerFactory.getLogger(TestData.class); + + private static final List PROTECTED_USERS = asList("scmadmin", "anonymous"); + + public static void cleanup() { + cleanupRepositories(); + cleanupGroups(); + cleanupUsers(); + } + + public static void cleanupRepositories() { + List repositories = given(VndMediaType.REPOSITORY_COLLECTION) + .when() + .get(createResourceUrl("repositories")) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .body().jsonPath().getList("_embedded.repositories._links.self.href"); + LOG.info("about to delete {} repositories", repositories.size()); + repositories.forEach(TestData::delete); + } + + public static void cleanupGroups() { + List groups = given(VndMediaType.GROUP_COLLECTION) + .when() + .get(createResourceUrl("groups")) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .body().jsonPath().getList("_embedded.groups._links.self.href"); + LOG.info("about to delete {} groups", groups.size()); + groups.forEach(TestData::delete); + } + + public static void cleanupUsers() { + List users = given(VndMediaType.USER_COLLECTION) + .when() + .get(createResourceUrl("users")) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .body().jsonPath().getList("_embedded.users._links.self.href"); + LOG.info("about to delete {} users", users.size()); + users.stream().filter(url -> PROTECTED_USERS.stream().noneMatch(url::contains)).forEach(TestData::delete); + } + + public static void delete(String url) { + given(VndMediaType.REPOSITORY) + .when() + .delete(url) + .then() + .statusCode(HttpStatus.SC_NO_CONTENT); + LOG.info("deleted {}", url); + } + + public static void main(String[] args) { + cleanup(); + } +} diff --git a/scm-it/src/test/resources/repository-git.json b/scm-it/src/test/resources/repository-git.json new file mode 100644 index 0000000000..3fc491ac01 --- /dev/null +++ b/scm-it/src/test/resources/repository-git.json @@ -0,0 +1,7 @@ +{ + "contact": "zaphod.beeblebrox@hitchhiker.com", + "description": "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive", + "name": "HeartOfGold-git", + "archived": false, + "type": "git" +} diff --git a/scm-it/src/test/resources/repository-hg.json b/scm-it/src/test/resources/repository-hg.json new file mode 100644 index 0000000000..cf1be4cc2e --- /dev/null +++ b/scm-it/src/test/resources/repository-hg.json @@ -0,0 +1,7 @@ +{ + "contact": "zaphod.beeblebrox@hitchhiker.com", + "description": "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive", + "name": "HeartOfGold-hg", + "archived": false, + "type": "hg" +} diff --git a/scm-it/src/test/resources/repository-svn.json b/scm-it/src/test/resources/repository-svn.json new file mode 100644 index 0000000000..97e2aa6074 --- /dev/null +++ b/scm-it/src/test/resources/repository-svn.json @@ -0,0 +1,7 @@ +{ + "contact": "zaphod.beeblebrox@hitchhiker.com", + "description": "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive", + "name": "HeartOfGold-svn", + "archived": false, + "type": "svn" +} diff --git a/scm-webapp/src/test/java/sonia/scm/it/GetRepositoriesITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GetRepositoriesITCase.java deleted file mode 100644 index 6978c897f8..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/it/GetRepositoriesITCase.java +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.it; - -//~--- non-JDK imports -------------------------------------------------------- - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryTestData; -import sonia.scm.util.IOUtil; - -import static org.junit.Assert.*; - -import static sonia.scm.it.IntegrationTestUtil.*; -import static sonia.scm.it.RepositoryITUtil.*; - -//~--- JDK imports ------------------------------------------------------------ - -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; - -import java.io.IOException; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * - * @author Sebastian Sdorra - */ -@RunWith(Parameterized.class) -public class GetRepositoriesITCase extends AbstractAdminITCaseBase -{ - - /** - * Constructs ... - * - * - * @param repositoryType - */ - public GetRepositoriesITCase(String repositoryType) - { - this.repositoryType = repositoryType; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - @Parameters - public static Collection createParameters() - { - Collection params = new ArrayList(); - - params.add(new String[] { "git" }); - params.add(new String[] { "svn" }); - - if (IOUtil.search("hg") != null) - { - params.add(new String[] { "hg" }); - } - - return params; - } - - /** - * Method description - * - * - * @throws IOException - */ - @After - public void cleanup() throws IOException - { - deleteRepository(client, repository.getId()); - } - - /** - * Method description - * - */ - @Test - public void testGetById() - { - repository = RepositoryTestData.createHeartOfGold(repositoryType); - repository = createRepository(client, repository); - - String id = repository.getId(); - - assertNotNull(id); - - Repository r = getRepositoryById(client, id); - - assertEquals(id, r.getId()); - } - - /** - * Method description - * - */ - @Test - public void testGetByTypeAndName() - { - repository = RepositoryTestData.create42Puzzle(repositoryType); - testGetByTypeAndName(repository); - } - - /** - * Method description - * - */ - @Test - public void testGetByTypeAndNameWithDirectoryStructure() - { - repository = - RepositoryTestData.createRestaurantAtTheEndOfTheUniverse(repositoryType); - repository.setName("test/".concat(repository.getName())); - testGetByTypeAndName(repository); - } - - /** - * Method description - * - * - * @param repository - */ - private void testGetByTypeAndName(Repository repo) - { - repository = createRepository(client, repo); - - String name = repository.getName(); - WebResource wr = createResource( - client, - "repositories/".concat(repositoryType).concat( - "/").concat(name)); - ClientResponse response = wr.get(ClientResponse.class); - - assertNotNull(response); - - Repository r = response.getEntity(Repository.class); - - response.close(); - assertNotNull(r); - assertEquals(repository.getId(), r.getId()); - assertEquals(repository.getName(), r.getName()); - assertEquals(repository.getType(), r.getType()); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Repository repository; - - /** Field description */ - private String repositoryType; -}