Introduce new integration test module

This commit is contained in:
René Pfeuffer
2018-08-03 09:38:13 +02:00
parent fd873877c4
commit 62f8509e85
11 changed files with 591 additions and 196 deletions

View File

@@ -73,6 +73,7 @@
<module>scm-ui</module>
<module>scm-webapp</module>
<module>scm-server</module>
<module>scm-it</module>
</modules>
<repositories>

173
scm-it/pom.xml Normal file
View File

@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>sonia.scm</groupId>
<artifactId>scm</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<groupId>sonia.scm</groupId>
<artifactId>scm-it</artifactId>
<packaging>jar</packaging>
<version>2.0.0-SNAPSHOT</version>
<name>scm-it</name>
<dependencies>
<dependency>
<groupId>sonia.scm</groupId>
<artifactId>scm-core</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila.maven-license-plugin</groupId>
<artifactId>maven-license-plugin</artifactId>
<version>1.9.0</version>
<configuration>
<header>http://download.scm-manager.org/licenses/mvn-license.txt</header>
<includes>
<include>src/**</include>
<include>**/test/**</include>
</includes>
<excludes>
<exclude>target/**</exclude>
<exclude>.hg/**</exclude>
<exclude>src/main/webapp/resources/extjs/**</exclude>
<exclude>src/main/webapp/resources/syntaxhighlighter/**</exclude>
<exclude>src/main/webapp/resources/moment/**</exclude>
<exclude>**/*.mustache</exclude>
</excludes>
<strictCheck>true</strictCheck>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.maven.version}</version>
<configuration>
<stopPort>8005</stopPort>
<stopKey>STOP</stopKey>
<systemProperties>
<systemProperty>
<name>scm.home</name>
<value>${scm.home}</value>
</systemProperty>
<systemProperty>
<name>scm.stage</name>
<value>${scm.stage}</value>
</systemProperty>
<systemProperty>
<name>java.awt.headless</name>
<value>true</value>
</systemProperty>
</systemProperties>
<webApp>
<contextPath>/scm</contextPath>
</webApp>
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>
<scanIntervalSeconds>0</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
<finalName>scm-it</finalName>
</build>
<properties>
</properties>
<profiles>
<profile>
<id>it</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12</version>
<configuration>
<includes>
<include>sonia/scm/it/*ITCase.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.maven.version}</version>
<configuration>
<stopPort>8085</stopPort>
<stopKey>STOP</stopKey>
<systemProperties>
<systemProperty>
<name>scm.home</name>
<value>target/scm-it</value>
</systemProperty>
<systemProperty>
<name>scm.stage</name>
<value>${scm.stage}</value>
</systemProperty>
</systemProperties>
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>
<war>${project.basedir}/../scm-webapp/target/scm-webapp.war</war>
<scanIntervalSeconds>0</scanIntervalSeconds>
<daemon>true</daemon>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>deploy-war</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!--*
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
-->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<!-- increase header size for mercurial -->
<Set name="requestHeaderSize">16384</Set>
<Set name="responseHeaderSize">16384</Set>
</New>
<Call id="httpConnector" name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref refid="Server" />
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref refid="httpConfig" />
</Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host">
<Property name="jetty.host" />
</Set>
<Set name="port">
<Property name="jetty.port" default="8081" />
</Set>
<Set name="idleTimeout">
<Property name="http.timeout" default="30000"/>
</Set>
</New>
</Arg>
</Call>
</Configure>

View File

@@ -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<String> {
public static Matcher<String> 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();
}
}

View File

@@ -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<String[]> createParameters() {
Collection<String[]> 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");
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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<String> PROTECTED_USERS = asList("scmadmin", "anonymous");
public static void cleanup() {
cleanupRepositories();
cleanupGroups();
cleanupUsers();
}
public static void cleanupRepositories() {
List<String> 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<String> 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<String> 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();
}
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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<String[]> createParameters()
{
Collection<String[]> params = new ArrayList<String[]>();
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;
}