Added docker based integration testing

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
master
Andrew DeMaria 6 years ago
parent 685f4fa7e5
commit 004b8bba37
No known key found for this signature in database
GPG Key ID: 0A3F5E91F8364EDF
  1. 4
      .travis.yml
  2. 3
      install/docker/Dockerfile
  3. 205
      integration-test/pom.xml
  4. 12
      integration-test/src/test/java/org/airsonic/test/SpringContext.java
  5. 13
      integration-test/src/test/java/org/airsonic/test/cucumber/RunCukesTest.java
  6. 20
      integration-test/src/test/java/org/airsonic/test/cucumber/server/AirsonicServer.java
  7. 50
      integration-test/src/test/java/org/airsonic/test/cucumber/steps/api/PingStepDef.java
  8. 61
      integration-test/src/test/java/org/airsonic/test/cucumber/steps/api/ScanStepDef.java
  9. 20
      integration-test/src/test/java/org/airsonic/test/cucumber/steps/api/SpringStepDef.java
  10. 69
      integration-test/src/test/java/org/airsonic/test/cucumber/steps/api/StreamStepDef.java
  11. 158
      integration-test/src/test/java/org/airsonic/test/cucumber_hooks/docker/DynamicDockerHook.java
  12. 98
      integration-test/src/test/java/org/airsonic/test/cucumber_hooks/docker/ExistingDockerHook.java
  13. 12
      integration-test/src/test/resources/application.properties
  14. 4
      integration-test/src/test/resources/blobs/ping/missing-auth.xml
  15. 2
      integration-test/src/test/resources/blobs/ping/ok.xml
  16. BIN
      integration-test/src/test/resources/blobs/stream/piano/piano.mp3
  17. 5
      integration-test/src/test/resources/features/api/ping.feature
  18. 11
      integration-test/src/test/resources/features/api/stream-mp3.feature
  19. 14
      integration-test/src/test/resources/logback.xml
  20. 24
      pom.xml

@ -2,6 +2,8 @@ language: java
sudo: required sudo: required
jdk: jdk:
- oraclejdk8 - oraclejdk8
services:
- docker
cache: cache:
directories: directories:
- $HOME/.m2 - $HOME/.m2
@ -13,4 +15,4 @@ before_script:
- export M2_HOME=$PWD/apache-maven-3.5.4 - export M2_HOME=$PWD/apache-maven-3.5.4
- export PATH=$PWD/apache-maven-3.5.4/bin:$PATH - export PATH=$PWD/apache-maven-3.5.4/bin:$PATH
script: script:
- mvn package install - mvn verify -P integration-test

@ -16,6 +16,7 @@ RUN apk --no-cache add \
ttf-dejavu \ ttf-dejavu \
ca-certificates \ ca-certificates \
tini \ tini \
curl \
openjdk8-jre openjdk8-jre
COPY run.sh /usr/local/bin/run.sh COPY run.sh /usr/local/bin/run.sh
@ -28,6 +29,6 @@ EXPOSE $AIRSONIC_PORT
VOLUME $AIRSONIC_DIR/data $AIRSONIC_DIR/music $AIRSONIC_DIR/playlists $AIRSONIC_DIR/podcasts VOLUME $AIRSONIC_DIR/data $AIRSONIC_DIR/music $AIRSONIC_DIR/playlists $AIRSONIC_DIR/podcasts
HEALTHCHECK CMD wget -q http://localhost:"$AIRSONIC_PORT""$CONTEXT_PATH"/rest/ping -O /dev/null || exit 1 HEALTHCHECK --interval=15s --timeout=3s CMD wget -q http://localhost:"$AIRSONIC_PORT""$CONTEXT_PATH"rest/ping -O /dev/null || exit 1
ENTRYPOINT ["tini", "--", "run.sh"] ENTRYPOINT ["tini", "--", "run.sh"]

@ -0,0 +1,205 @@
<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>
<artifactId>airsonic-integration-test</artifactId>
<name>Airsonic Integration Test</name>
<parent>
<groupId>org.airsonic.player</groupId>
<artifactId>airsonic</artifactId>
<version>10.2.0-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<cucumber.version>2.3.1</cucumber.version>
</properties>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java8</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.spotify</groupId>
<artifactId>docker-client</artifactId>
<version>8.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.airsonic.player</groupId>
<artifactId>subsonic-rest-api</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>com.github.temyers</groupId>
<artifactId>cucumber-jvm-parallel-plugin</artifactId>
<version>5.0.0</version>
<executions>
<execution>
<id>generateRunners</id>
<phase>generate-test-sources</phase>
<goals>
<goal>generateRunners</goal>
</goals>
<configuration>
<featuresDirectory>src/test/resources/features</featuresDirectory>
<format>pretty</format>
<glue>org.airsonic.test.cucumber.steps</glue>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerVersion>1.8</compilerVersion>
<showWarnings>true</showWarnings>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>default-jar</id>
<phase/>
<configuration>
<finalName>unwanted</finalName>
<classifier>unwanted</classifier>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<parallel>all</parallel>
<threadCount>2</threadCount>
<includes>
<include>**/Parallel*IT.class</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,12 @@
package org.airsonic.test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("org.airsonic.test")
@PropertySource("classpath:application.properties")
public class SpringContext {
}

@ -0,0 +1,13 @@
package org.airsonic.test.cucumber;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty"},
features = "classpath:features/api/stream-mp3.feature",
glue = "org.airsonic.test.cucumber.steps")
public class RunCukesTest {
}

@ -0,0 +1,20 @@
package org.airsonic.test.cucumber.server;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.client.methods.RequestBuilder;
import java.nio.file.Path;
public interface AirsonicServer {
String getBaseUri();
void uploadToDefaultMusicFolder(Path directoryPath, String relativePath);
default void addRestParameters(RequestBuilder builder) {
builder.addParameter("c", "inttest");
builder.addParameter("v", "1.15.0");
builder.addParameter("u", "admin");
builder.addParameter("s", "int");
builder.addParameter("t", DigestUtils.md5Hex("admin" + "int"));
}
}

@ -0,0 +1,50 @@
package org.airsonic.test.cucumber.steps.api;
import cucumber.api.java8.En;
import org.airsonic.test.cucumber.server.AirsonicServer;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.xmlunit.builder.Input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.xmlunit.matchers.CompareMatcher.isIdenticalTo;
public class PingStepDef implements En {
private CloseableHttpResponse response;
private CloseableHttpClient client;
public PingStepDef(AirsonicServer server) {
this.client = HttpClientBuilder.create().build();
When("^A ping request is sent$", () -> {
HttpGet httpGet = new HttpGet(server.getBaseUri() + "/rest/ping");
this.response = client.execute(httpGet);
});
Then("^A required parameter response is received$", () -> {
if(response == null) {
throw new IllegalStateException();
}
try {
StatusLine statusLine = response.getStatusLine();
assertEquals(statusLine.getStatusCode(), 200);
HttpEntity entity = response.getEntity();
String actual = EntityUtils.toString(entity);
assertThat(
actual,
isIdenticalTo(
Input.fromStream(
getClass().getResourceAsStream("/blobs/ping/missing-auth.xml")))
.ignoreWhitespace());
} finally {
response.close();
}
});
}
}

@ -0,0 +1,61 @@
package org.airsonic.test.cucumber.steps.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import cucumber.api.java8.En;
import org.airsonic.test.cucumber.server.AirsonicServer;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import org.subsonic.restapi.Response;
import java.io.IOException;
public class ScanStepDef implements En {
private final AirsonicServer server;
private CloseableHttpResponse response;
private CloseableHttpClient client;
private ObjectMapper mapper = new ObjectMapper();
public ScanStepDef(AirsonicServer server) {
mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
this.client = HttpClientBuilder.create().build();
this.server = server;
Given("a scan is done", () -> {
Assert.assertFalse(isScanning());
RequestBuilder builder = RequestBuilder.create("GET").setUri(server.getBaseUri() + "/rest/startScan");
server.addRestParameters(builder);
response = client.execute(builder.build());
System.out.println(EntityUtils.toString(response.getEntity()));
Long waitTime = 30000L;
Long sleepTime = 1000L;
while(waitTime > 0 && isScanning()) {
waitTime -= sleepTime;
Thread.sleep(sleepTime);
}
Assert.assertFalse(isScanning());
});
}
private boolean isScanning() throws IOException {
RequestBuilder builder = RequestBuilder.create("GET").setUri(server.getBaseUri() + "/rest/getScanStatus");
builder.addParameter("f", "json");
server.addRestParameters(builder);
response = client.execute(builder.build());
String responseAsString = EntityUtils.toString(response.getEntity());
JsonNode jsonNode = mapper.readTree(responseAsString).get("subsonic-response");
Response response = mapper.treeToValue(jsonNode, Response.class);
return response.getScanStatus().isScanning();
}
}

@ -0,0 +1,20 @@
package org.airsonic.test.cucumber.steps.api;
import cucumber.api.java.Before;
import org.airsonic.test.SpringContext;
import org.airsonic.test.cucumber.server.AirsonicServer;
import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration(classes = SpringContext.class)
public class SpringStepDef {
public SpringStepDef(AirsonicServer server) {
}
@Before
public void setup_cucumber_spring_context(){
// Dummy method so cucumber will recognize this class as glue
// and use its context configuration.
}
}

@ -0,0 +1,69 @@
package org.airsonic.test.cucumber.steps.api;
import cucumber.api.java8.En;
import org.airsonic.test.cucumber.server.AirsonicServer;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.HexDump;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
public class StreamStepDef implements En {
private CloseableHttpResponse response;
private CloseableHttpClient client;
private boolean closed = false;
private byte[] body;
public StreamStepDef(AirsonicServer server) {
this.client = HttpClientBuilder.create().build();
Given("Media file (.*) is added", (String mediaFile) -> {
// TODO fix this
server.uploadToDefaultMusicFolder(
Paths.get(this.getClass().getResource("/blobs/stream/piano").toURI()),
"");
});
When("A stream request is sent", () -> {
RequestBuilder builder = RequestBuilder.create("GET").setUri(server.getBaseUri() + "/rest/stream");
builder.addParameter("id", "2");
builder.addHeader("Range", "bytes=0-");
builder.addHeader("Accept", "audio/webm,audio/ogg,audio/wav,audio/*;");
server.addRestParameters(builder);
response = client.execute(builder.build());
});
Then("The response bytes are equal", () -> {
ensureBodyRead();
FileUtils.writeByteArrayToFile(new File("/tmp/bytearray"), body);
byte[] expected = IOUtils.toByteArray(this.getClass().getResource("/blobs/stream/piano/piano.mp3").toURI());
//
// HexDump.dump(expected, 0, System.out, 0);
Assert.assertArrayEquals(expected, body);
});
}
void ensureBodyRead() throws IOException {
if(closed) {
return;
} else {
this.body = EntityUtils.toByteArray(response.getEntity());
closed = true;
response.close();
}
}
}

@ -0,0 +1,158 @@
package org.airsonic.test.cucumber_hooks.docker;
import com.spotify.docker.client.DefaultDockerClient;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.exceptions.DockerException;
import com.spotify.docker.client.messages.*;
import org.airsonic.test.cucumber.server.AirsonicServer;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import static com.spotify.docker.client.DockerClient.RemoveContainerParam.*;
@Component
@Profile("dynamic")
public class DynamicDockerHook implements AirsonicServer, EnvironmentAware, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(DynamicDockerHook.class);
public static final String AIRSONIC_DOCKER_IMAGE = "airsonic.docker.image";
public static final String AIRSONIC_DOCKER_PORT = "airsonic.docker.port";
public static final String AIRSONIC_READY_MAX_WAIT = "airsonic.ready.max_wait";
public static final String AIRSONIC_READY_SLEEP_TIME = "airsonic.ready.sleep_time";
private String serverUri = null;
private final DockerClient docker;
private String containerId;
private String dockerImage;
private Integer dockerPort;
private Long readyMaxWaitTime;
private Long readySleepTime;
public DynamicDockerHook() {
logger.debug("Using hook for dynamically creating containers");
docker = new DefaultDockerClient("unix:///var/run/docker.sock");
testDockerIsAvail();
}
private void testDockerIsAvail() {
try {
logger.trace("Trying to ping docker daemon");
docker.ping();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String getBaseUri() {
if (serverUri == null) {
throw new IllegalStateException("Server is not yet ready");
} else {
return serverUri;
}
}
@Override
public void uploadToDefaultMusicFolder(Path localDir, String relativePath) {
try {
// TODO ensure localDir is a directory
docker.copyToContainer(localDir, containerId, "/airsonic/music/" + relativePath);
} catch (DockerException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
public void startServer() {
logger.debug("Starting server");
final ContainerConfig config = ContainerConfig.builder()
.image(dockerImage)
.build();
final String name = "airsonic-it-" + RandomStringUtils.randomAlphabetic(10);
try {
final ContainerCreation containerCreate = docker.createContainer(config, name);
containerId = containerCreate.id();
docker.startContainer(containerId);
Long waitTime = readyMaxWaitTime;
while(true) {
ContainerInfo containerInfo = docker.inspectContainer(containerId);
ContainerState.Health health = containerInfo.state().health();
if (health != null && StringUtils.equalsIgnoreCase(health.status(), "healthy")) {
logger.trace("Container started early. Yay!");
break;
} else if(waitTime > readySleepTime) {
if(logger.isTraceEnabled()) {
String message;
if(health != null) {
message = "Container ("+name+") not yet ready. State was: " + health.status();
} else {
message = "Container ("+name+") state unknown. Waiting";
}
logger.trace(message);
}
waitTime -= readySleepTime;
Thread.sleep(readySleepTime);
} else if(health == null) {
logger.trace("Max wait time with unknown container state. Hoping container is ready");
break;
} else {
logger.trace("Container ("+name+") never became ready within max wait time");
throw new RuntimeException("Container ("+name+") not ready");
}
}
ContainerInfo containerInfo = docker.inspectContainer(containerId);
try {
Map.Entry<String, AttachedNetwork> next = containerInfo.networkSettings().networks().entrySet().iterator().next();
String ipAddress = next.getValue().ipAddress();
serverUri = "http://" + ipAddress + ":" + dockerPort;
} catch(Exception e) {
throw new RuntimeException("Could not determine container ("+name+") address", e);
}
} catch (DockerException | InterruptedException e) {
throw new RuntimeException(e);
}
}
public void stopServer() {
if(containerId != null) {
try {
docker.removeContainer(containerId, forceKill(), removeVolumes());
} catch (DockerException | InterruptedException e) {
throw new RuntimeException("Could not remove container", e);
}
}
}
@Override
public void setEnvironment(Environment environment) {
dockerImage = environment.getRequiredProperty(AIRSONIC_DOCKER_IMAGE);
dockerPort = Integer.parseInt(environment.getRequiredProperty(AIRSONIC_DOCKER_PORT));
readyMaxWaitTime = Long.parseLong(environment.getRequiredProperty(AIRSONIC_READY_MAX_WAIT));
readySleepTime = Long.parseLong(environment.getRequiredProperty(AIRSONIC_READY_SLEEP_TIME));
if(readyMaxWaitTime <= 0L || readySleepTime <= 0L) {
throw new IllegalArgumentException("Max wait time and sleep time must be greater than 0");
}
}
@Override
public void destroy() throws Exception {
stopServer();
}
@Override
public void afterPropertiesSet() throws Exception {
startServer();
}
}

@ -0,0 +1,98 @@
package org.airsonic.test.cucumber_hooks.docker;
import com.spotify.docker.client.DefaultDockerClient;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.exceptions.DockerException;
import com.spotify.docker.client.messages.AttachedNetwork;
import com.spotify.docker.client.messages.ContainerInfo;
import org.airsonic.test.cucumber.server.AirsonicServer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
@Component
@Profile("existing")
public class ExistingDockerHook implements AirsonicServer, EnvironmentAware, InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(ExistingDockerHook.class);
public static final String AIRSONIC_DOCKER_CONTAINER = "airsonic.docker.container";
public static final String AIRSONIC_DOCKER_PORT = "airsonic.docker.port";
private String serverUri = null;
private final DockerClient docker;
private String containerId;
private Integer dockerPort;
public ExistingDockerHook() {
logger.debug("Using hook for existing docker container");
docker = new DefaultDockerClient("unix:///var/run/docker.sock");
testDockerIsAvail();
}
private void testDockerIsAvail() {
try {
logger.trace("Trying to ping docker daemon");
docker.ping();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String getBaseUri() {
if (serverUri == null) {
throw new IllegalStateException("Server is not yet ready");
} else {
return serverUri;
}
}
@Override
public void uploadToDefaultMusicFolder(Path localDir, String relativePath) {
try {
// TODO ensure localDir is a directory
docker.copyToContainer(localDir, containerId, "/airsonic/music/" + relativePath);
} catch (DockerException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
public void connectToServer() {
logger.debug("Connecting to server");
try {
ContainerInfo containerInfo = docker.inspectContainer(containerId);
if(!containerInfo.state().running()) {
throw new IllegalStateException("Container is not running " + containerId);
}
Map.Entry<String, AttachedNetwork> next = containerInfo.networkSettings().networks().entrySet().iterator().next();
String ipAddress = next.getValue().ipAddress();
if(StringUtils.isBlank(ipAddress)) {
throw new IllegalStateException("No address found for container " + containerId);
}
serverUri = "http://" + ipAddress + ":" + dockerPort;
} catch (DockerException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void setEnvironment(Environment environment) {
containerId = environment.getRequiredProperty(AIRSONIC_DOCKER_CONTAINER);
dockerPort = Integer.parseInt(environment.getRequiredProperty(AIRSONIC_DOCKER_PORT));
}
@Override
public void afterPropertiesSet() throws Exception {
connectToServer();
}
}

@ -0,0 +1,12 @@
airsonic.docker.port=4040
airsonic.ready.max_wait=80000
airsonic.ready.sleep_time=5000
# Use for dynamically generating a container as needed
spring.profiles.active=dynamic
airsonic.docker.image=airsonic/airsonic:${project.version}
# Use for testing against an existing/running container
#airsonic.docker.container=1212be8a94e0
#spring.profiles.active=existing

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<subsonic-response xmlns="http://subsonic.org/restapi" status="failed" version="1.15.0">
<error code="10" message="Required parameter is missing."/>
</subsonic-response>

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.15.0"/>

@ -0,0 +1,5 @@
Feature: Ping API
Scenario: Airsonic responds to ping requests
When A ping request is sent
Then A required parameter response is received

@ -0,0 +1,11 @@
Feature: Stream API for MP3
Background:
Given Media file stream/piano/piano.mp3 is added
And a scan is done
Scenario: Airsonic sends stream data
When A stream request is sent
Then The response bytes are equal
# TODO check length

@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger level="trace" name="org.airsonic.test.cucumber_hooks.docker.DockerHook" />
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

@ -116,12 +116,22 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId> <artifactId>jackson-core</artifactId>
<version>2.8.11</version> <version>2.9.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.8.11.1</version> <version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -303,6 +313,16 @@
<module>install/docker</module> <module>install/docker</module>
</modules> </modules>
</profile> </profile>
<profile>
<id>integration-test</id>
<modules>
<module>subsonic-rest-api</module>
<module>airsonic-sonos-api</module>
<module>airsonic-main</module>
<module>install/docker</module>
<module>integration-test</module>
</modules>
</profile>
<profile> <profile>
<id>sign</id> <id>sign</id>
<build> <build>

Loading…
Cancel
Save