diff --git a/api/README.md b/api/README.md index 415ce6159..6ad04cb70 100644 --- a/api/README.md +++ b/api/README.md @@ -100,3 +100,19 @@ Some features will be not sufficiently tested and will only be enabled if you ad --seedNodes=localhost:8000 --btcNodes=localhost:18445 --baseCurrencyNetwork=BTC_REGTEST --logLevel=info --useDevPrivilegeKeys=true --bitcoinRegtestHost=NONE --myAddress=172.17.0.1:8003 --enableHttpApiExperimentalFeatures" + +## Integration tests + +Integration tests leverage Docker and run in headless mode. First you need to build docker images for the api: + + cd api + docker-compose build + ../gradlew testIntegration + +IntelliJ Idea has awesome integration so just right click on `api/src/testIntegration` directory and select +`Debug All Tests`. + +### Integration tests logging + +Due to Travis log length limitations the log level is set to WARN, but if you need to see more details locally +go to `ContainerFactory` class and set `ENV_LOG_LEVEL_VALUE` property to `debug`. diff --git a/api/src/main/java/bisq/api/http/service/endpoint/UserEndpoint.java b/api/src/main/java/bisq/api/http/service/endpoint/UserEndpoint.java index bb16b0899..9bda23b95 100644 --- a/api/src/main/java/bisq/api/http/service/endpoint/UserEndpoint.java +++ b/api/src/main/java/bisq/api/http/service/endpoint/UserEndpoint.java @@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import javax.validation.Valid; +import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -54,7 +55,7 @@ public UserEndpoint(ApiPasswordManager apiPasswordManager) { @Operation(summary = "Change password") @POST @Path("/password") - public void changePassword(@Suspended AsyncResponse asyncResponse, @Valid ChangePassword data) { + public void changePassword(@Suspended AsyncResponse asyncResponse, @NotNull @Valid ChangePassword data) { UserThread.execute(() -> { try { apiPasswordManager.changePassword(data.oldPassword, data.newPassword); diff --git a/api/src/testIntegration/java/bisq/api/http/ApiTestHelper.java b/api/src/testIntegration/java/bisq/api/http/ApiTestHelper.java new file mode 100644 index 000000000..42788b38c --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/ApiTestHelper.java @@ -0,0 +1,30 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http; + +@SuppressWarnings("WeakerAccess") +public final class ApiTestHelper { + + public static void waitForAllServicesToBeReady() throws InterruptedException { +// TODO it would be nice to expose endpoint that would respond with 200 + // PaymentMethod initializes it's static values after all services get initialized + int ALL_SERVICES_INITIALIZED_DELAY = 5000; + Thread.sleep(ALL_SERVICES_INITIALIZED_DELAY); + } + +} diff --git a/api/src/testIntegration/java/bisq/api/http/ContainerFactory.java b/api/src/testIntegration/java/bisq/api/http/ContainerFactory.java new file mode 100644 index 000000000..d7a67612d --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/ContainerFactory.java @@ -0,0 +1,101 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http; + +import org.arquillian.cube.docker.impl.client.config.Await; +import org.arquillian.cube.docker.impl.client.containerobject.dsl.Container; +import org.arquillian.cube.docker.impl.client.containerobject.dsl.ContainerBuilder; + +@SuppressWarnings("WeakerAccess") +public final class ContainerFactory { + + public static final String BITCOIN_NODE_CONTAINER_NAME = "bisq-api-bitcoin-node"; + public static final String BITCOIN_NODE_HOST_NAME = "bitcoin"; + public static final String SEED_NODE_CONTAINER_NAME = "bisq-seednode"; + public static final String SEED_NODE_HOST_NAME = SEED_NODE_CONTAINER_NAME; + public static final String SEED_NODE_ADDRESS = SEED_NODE_HOST_NAME + ":8000"; + public static final String CONTAINER_NAME_PREFIX = "bisq-api-"; + public static final String API_IMAGE = "bisq/api"; + public static final String ENV_NODE_PORT_KEY = "NODE_PORT"; + public static final String ENV_ENABLE_HTTP_API_EXPERIMENTAL_FEATURES_KEY = "ENABLE_HTTP_API_EXPERIMENTAL_FEATURES"; + public static final String ENV_HTTP_API_HOST_KEY = "HTTP_API_HOST"; + public static final String ENV_HTTP_API_HOST_VALUE = "0.0.0.0"; + public static final String ENV_USE_DEV_PRIVILEGE_KEYS_KEY = "USE_DEV_PRIVILEGE_KEYS"; + public static final String ENV_USE_DEV_PRIVILEGE_KEYS_VALUE = "true"; + public static final String ENV_USE_LOCALHOST_FOR_P2P_KEY = "USE_LOCALHOST_FOR_P2P"; + public static final String ENV_USE_LOCALHOST_FOR_P2P_VALUE = "true"; + public static final String ENV_BASE_CURRENCY_NETWORK_KEY = "BASE_CURRENCY_NETWORK"; + public static final String ENV_BASE_CURRENCY_NETWORK_VALUE = "BTC_REGTEST"; + public static final String ENV_BITCOIN_REGTEST_HOST_KEY = "BITCOIN_REGTEST_HOST"; + public static final String ENV_BITCOIN_REGTEST_HOST_VALUE = "LOCALHOST"; + public static final String ENV_BTC_NODES_KEY = "BTC_NODES"; + public static final String ENV_BTC_NODES_VALUE = "bitcoin:18444"; + public static final String ENV_SEED_NODES_KEY = "SEED_NODES"; + public static final String ENV_SEED_NODES_VALUE = SEED_NODE_ADDRESS; + public static final String ENV_LOG_LEVEL_KEY = "LOG_LEVEL"; + public static final String ENV_LOG_LEVEL_VALUE = "warn"; + + @SuppressWarnings("WeakerAccess") + public static ContainerBuilder.ContainerOptionsBuilder createApiContainerBuilder(String nameSuffix, String portBinding, int nodePort, boolean linkToSeedNode, boolean linkToBitcoin, boolean enableExperimentalFeatures) { + ContainerBuilder.ContainerOptionsBuilder containerOptionsBuilder = Container.withContainerName(CONTAINER_NAME_PREFIX + nameSuffix) + .fromImage(API_IMAGE) + .withPortBinding(portBinding) + .withEnvironment(ENV_NODE_PORT_KEY, nodePort) + .withEnvironment(ENV_HTTP_API_HOST_KEY, ENV_HTTP_API_HOST_VALUE) + .withEnvironment(ENV_ENABLE_HTTP_API_EXPERIMENTAL_FEATURES_KEY, enableExperimentalFeatures) + .withEnvironment(ENV_USE_DEV_PRIVILEGE_KEYS_KEY, ENV_USE_DEV_PRIVILEGE_KEYS_VALUE) + .withAwaitStrategy(getAwaitStrategy()); + if (linkToSeedNode) { + containerOptionsBuilder.withLink(SEED_NODE_CONTAINER_NAME); + } + if (linkToBitcoin) { + containerOptionsBuilder.withLink(BITCOIN_NODE_CONTAINER_NAME, BITCOIN_NODE_HOST_NAME); + } + return withRegtestEnv(containerOptionsBuilder); + } + + public static Await getAwaitStrategy() { + Await awaitStrategy = new Await(); + awaitStrategy.setStrategy("polling"); + int sleepPollingTime = 250; + awaitStrategy.setIterations(60000 / sleepPollingTime); + awaitStrategy.setSleepPollingTime(sleepPollingTime); + return awaitStrategy; + } + + public static Container createApiContainer(String nameSuffix, String portBinding, int nodePort, boolean linkToSeedNode, boolean linkToBitcoin, boolean enableExperimentalFeatures) { + Container container = createApiContainerBuilder(nameSuffix, portBinding, nodePort, linkToSeedNode, linkToBitcoin, enableExperimentalFeatures).build(); + container.getCubeContainer().setKillContainer(true); + return container; + } + + public static Container createApiContainer(String nameSuffix, String portBinding, int nodePort, boolean linkToSeedNode, boolean linkToBitcoin) { + return createApiContainer(nameSuffix, portBinding, nodePort, linkToSeedNode, linkToBitcoin, true); + } + + public static ContainerBuilder.ContainerOptionsBuilder withRegtestEnv(ContainerBuilder.ContainerOptionsBuilder builder) { + return builder + .withEnvironment(ENV_USE_LOCALHOST_FOR_P2P_KEY, ENV_USE_LOCALHOST_FOR_P2P_VALUE) + .withEnvironment(ENV_BASE_CURRENCY_NETWORK_KEY, ENV_BASE_CURRENCY_NETWORK_VALUE) + .withEnvironment(ENV_BITCOIN_REGTEST_HOST_KEY, ENV_BITCOIN_REGTEST_HOST_VALUE) + .withEnvironment(ENV_BTC_NODES_KEY, ENV_BTC_NODES_VALUE) + .withEnvironment(ENV_SEED_NODES_KEY, ENV_SEED_NODES_VALUE) + .withEnvironment(ENV_LOG_LEVEL_KEY, ENV_LOG_LEVEL_VALUE); + } + +} diff --git a/api/src/testIntegration/java/bisq/api/http/RegexMatcher.java b/api/src/testIntegration/java/bisq/api/http/RegexMatcher.java new file mode 100644 index 000000000..9c8109f06 --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/RegexMatcher.java @@ -0,0 +1,45 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +public class RegexMatcher extends TypeSafeMatcher { + + private final String regex; + + private RegexMatcher(String regex) { + this.regex = regex; + } + + @SuppressWarnings("WeakerAccess") + public static RegexMatcher matchesRegex(String regex) { + return new RegexMatcher(regex); + } + + @Override + public void describeTo(Description description) { + description.appendText("matches regex=`" + regex + "`"); + } + + @Override + public boolean matchesSafely(String string) { + return string.matches(regex); + } +} diff --git a/api/src/testIntegration/java/bisq/api/http/SwaggerIT.java b/api/src/testIntegration/java/bisq/api/http/SwaggerIT.java new file mode 100644 index 000000000..03633442a --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/SwaggerIT.java @@ -0,0 +1,78 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + + + +import org.arquillian.cube.docker.impl.client.containerobject.dsl.Container; +import org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerContainer; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; + +@RunWith(Arquillian.class) +public class SwaggerIT { + + @DockerContainer + private Container alice = ContainerFactory.createApiContainer("alice", "8081->8080", 3333, false, false); + + @InSequence(1) + @Test + public void getDocs_always_returns200() { + int alicePort = getAlicePort(); + + given(). + port(alicePort). +// + when(). + get("/docs"). +// + then(). + statusCode(200). + and().body(containsString("Swagger UI")) + ; + } + + @InSequence(1) + @Test + public void getOpenApiJson_always_returns200() { + int alicePort = getAlicePort(); + + given(). + port(alicePort). +// + when(). + get("/openapi.json"). +// + then(). + statusCode(200). + and().body("info.title", equalTo("Bisq HTTP API")) + ; + } + + private int getAlicePort() { + return alice.getBindPort(8080); + } + +} diff --git a/api/src/testIntegration/java/bisq/api/http/UserEndpointIT.java b/api/src/testIntegration/java/bisq/api/http/UserEndpointIT.java new file mode 100644 index 000000000..434c2a2a4 --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/UserEndpointIT.java @@ -0,0 +1,216 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http; + +import bisq.api.http.model.ChangePassword; + +import bisq.common.util.Base64; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static io.restassured.RestAssured.given; +import static org.junit.Assert.assertEquals; + + + +import com.github.javafaker.Faker; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import org.arquillian.cube.docker.impl.client.containerobject.dsl.Container; +import org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerContainer; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; + +@RunWith(Arquillian.class) +public class UserEndpointIT { + + private static String validPassword = new Faker().internet().password(); + private static String invalidPassword = getRandomPasswordDifferentThan(validPassword); + @DockerContainer + Container alice = ContainerFactory.createApiContainer("alice", "8081->8080", 3333, false, false); + + private static String getRandomPasswordDifferentThan(String otherPassword) { + String newPassword; + do { + newPassword = new Faker().internet().password(); + } while (otherPassword.equals(newPassword)); + return newPassword; + } + + @InSequence + @Test + public void waitForAllServicesToBeReady() throws InterruptedException { + ApiTestHelper.waitForAllServicesToBeReady(); + verifyThatAuthenticationIsDisabled(); + } + + @InSequence(2) + @Test + public void changePassword_missingPayload_returns400() { + int alicePort = getAlicePort(); + String body = given(). + port(alicePort). + contentType(ContentType.JSON). + accept(ContentType.JSON). +// + when(). + post("/api/v1/user/password"). +// + then(). + statusCode(400). + extract().asString(); + assertEquals("", body); + verifyThatAuthenticationIsDisabled(); + } + + @InSequence(3) + @Test + public void changePassword_settingFirstPassword_enablesAuthentication() { + int alicePort = getAlicePort(); + String body = given(). + port(alicePort). + body(new ChangePassword(validPassword, null)). + contentType(ContentType.JSON). +// + when(). + post("/api/v1/user/password"). +// + then(). + statusCode(204). + extract().asString(); + assertEquals("", body); + verifyThatAuthenticationIsEnabled(); + verifyThatPasswordIsValid(validPassword); + } + + @InSequence(4) + @Test + public void changePassword_invalidOldPassword_returns401() { + int alicePort = getAlicePort(); + String newPassword = getRandomPasswordDifferentThan(validPassword); + given(). + port(alicePort). + body(new ChangePassword(newPassword, invalidPassword)). + contentType(ContentType.JSON). +// + when(). + post("/api/v1/user/password"). +// + then(). + statusCode(401); + verifyThatAuthenticationIsEnabled(); + verifyThatPasswordIsValid(validPassword); + verifyThatPasswordIsInvalid(newPassword); + } + + @InSequence(4) + @Test + public void changePassword_emptyOldPassword_returns401() { + int alicePort = getAlicePort(); + String newPassword = getRandomPasswordDifferentThan(validPassword); + given(). + port(alicePort). + body(new ChangePassword(newPassword, null)). + contentType(ContentType.JSON). +// + when(). + post("/api/v1/user/password"). +// + then(). + statusCode(401); + verifyThatAuthenticationIsEnabled(); + verifyThatPasswordIsValid(validPassword); + verifyThatPasswordIsInvalid(newPassword); + } + + + @InSequence(5) + @Test + public void changePassword_settingAnotherPassword_keepsAuthenticationEnabled() { + int alicePort = getAlicePort(); + String oldPassword = validPassword; + String newPassword = getRandomPasswordDifferentThan(validPassword); + validPassword = newPassword; + invalidPassword = getRandomPasswordDifferentThan(validPassword); + given(). + port(alicePort). + body(new ChangePassword(newPassword, oldPassword)). + contentType(ContentType.JSON). +// + when(). + post("/api/v1/user/password"). +// + then(). + statusCode(204); + verifyThatPasswordIsInvalid(oldPassword); + verifyThatPasswordIsValid(newPassword); + verifyThatAuthenticationIsEnabled(); + } + + @InSequence(6) + @Test + public void changePassword_validOldPasswordAndNoNewPassword_disablesAuthentication() { + int alicePort = getAlicePort(); + given(). + port(alicePort). + body(new ChangePassword(null, validPassword)). + contentType(ContentType.JSON). +// + when(). + post("/api/v1/user/password"). +// + then(). + statusCode(204); + verifyThatAuthenticationIsDisabled(); + } + + private void verifyThatAuthenticationIsDisabled() { + authenticationVerificationRequest().then().statusCode(200); + } + + private void verifyThatAuthenticationIsEnabled() { + authenticationVerificationRequest().then().statusCode(401); + } + + private void verifyThatPasswordIsInvalid(String password) { + passwordVerificationRequest(password).then().statusCode(401); + } + + private void verifyThatPasswordIsValid(String password) { + passwordVerificationRequest(password).then().statusCode(200); + } + + private Response authenticationVerificationRequest() { + int alicePort = getAlicePort(); + return given().port(alicePort).when().get("/api/v1/version"); + } + + private Response passwordVerificationRequest(String password) { + int alicePort = getAlicePort(); + String authHeader = "Basic " + Base64.encode((":" + password).getBytes()); + return given().port(alicePort).when() + .header("authorization", authHeader) + .get("/api/v1/version"); + } + + private int getAlicePort() { + return alice.getBindPort(8080); + } + +} diff --git a/api/src/testIntegration/java/bisq/api/http/VersionEndpointIT.java b/api/src/testIntegration/java/bisq/api/http/VersionEndpointIT.java new file mode 100644 index 000000000..155b23741 --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/VersionEndpointIT.java @@ -0,0 +1,73 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http; + +import bisq.common.app.Version; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isA; + + + +import org.arquillian.cube.docker.impl.client.containerobject.dsl.Container; +import org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerContainer; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; + +@RunWith(Arquillian.class) +public class VersionEndpointIT { + + @DockerContainer + private Container alice = ContainerFactory.createApiContainer("alice", "8081->8080", 3333, false, false); + + @InSequence + @Test + public void waitForAllServicesToBeReady() throws InterruptedException { + ApiTestHelper.waitForAllServicesToBeReady(); + } + + @InSequence(1) + @Test + public void getVersionDetails_always_returns200() { + int alicePort = getAlicePort(); + + given(). + port(alicePort). +// + when(). + get("/api/v1/version"). +// + then(). + statusCode(200). + and().body("application", equalTo(Version.VERSION)). + and().body("network", equalTo(Version.P2P_NETWORK_VERSION)). + and().body("p2PMessage", isA(Integer.class)). + and().body("localDB", equalTo(Version.LOCAL_DB_VERSION)). + and().body("tradeProtocol", equalTo(Version.TRADE_PROTOCOL_VERSION)) + ; + } + + private int getAlicePort() { + return alice.getBindPort(8080); + } + +} diff --git a/api/src/testIntegration/java/bisq/api/http/arguillian/CubeLogger.java b/api/src/testIntegration/java/bisq/api/http/arguillian/CubeLogger.java new file mode 100644 index 000000000..5d1b9f53e --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/arguillian/CubeLogger.java @@ -0,0 +1,47 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http.arquillian; + +import org.arquillian.cube.CubeController; +import org.arquillian.cube.spi.event.lifecycle.BeforeStop; +import org.jboss.arquillian.config.descriptor.api.ArquillianDescriptor; +import org.jboss.arquillian.core.api.annotation.Observes; +import org.jboss.arquillian.test.spi.TestClass; + +public class CubeLogger { + + private static boolean isExtensionEnabled(ArquillianDescriptor arquillianDescriptor) { + String dumpContainerLogs = arquillianDescriptor.extension("cubeLogger").getExtensionProperty("enable"); + return Boolean.parseBoolean(dumpContainerLogs); + } + + @SuppressWarnings({"unused", "UnusedParameters"}) + public void beforeContainerStop(@Observes BeforeStop event, CubeController cubeController, ArquillianDescriptor arquillianDescriptor, TestClass testClass) { + if (isExtensionEnabled(arquillianDescriptor)) { + String cubeId = event.getCubeId(); + System.out.println("====================================================================================="); + System.out.println("Start of container logs: " + cubeId + " from " + testClass.getName()); + System.out.println("====================================================================================="); + cubeController.copyLog(cubeId, false, true, true, true, -1, System.out); + System.out.println("====================================================================================="); + System.out.println("End of container logs: " + cubeId + " from " + testClass.getName()); + System.out.println("====================================================================================="); + } + } + +} diff --git a/api/src/testIntegration/java/bisq/api/http/arguillian/CubeLoggerExtension.java b/api/src/testIntegration/java/bisq/api/http/arguillian/CubeLoggerExtension.java new file mode 100644 index 000000000..a7f342709 --- /dev/null +++ b/api/src/testIntegration/java/bisq/api/http/arguillian/CubeLoggerExtension.java @@ -0,0 +1,28 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.api.http.arquillian; + +import org.jboss.arquillian.core.spi.LoadableExtension; + +public class CubeLoggerExtension implements LoadableExtension { + + @Override + public void register(ExtensionBuilder extensionBuilder) { + extensionBuilder.observer(CubeLogger.class); + } +} diff --git a/api/src/testIntegration/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/api/src/testIntegration/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 000000000..2f2f5d333 --- /dev/null +++ b/api/src/testIntegration/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +bisq.api.http.arquillian.CubeLoggerExtension diff --git a/api/src/testIntegration/resources/arquillian.xml b/api/src/testIntegration/resources/arquillian.xml new file mode 100644 index 000000000..45de142b0 --- /dev/null +++ b/api/src/testIntegration/resources/arquillian.xml @@ -0,0 +1,13 @@ + + + + + CUBE + unix:///var/run/docker.sock + false + + + true + + diff --git a/api/src/testIntegration/resources/logback-test.xml b/api/src/testIntegration/resources/logback-test.xml new file mode 100644 index 000000000..d26ff1e63 --- /dev/null +++ b/api/src/testIntegration/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 29f5553c8..270750602 100644 --- a/build.gradle +++ b/build.gradle @@ -305,8 +305,47 @@ configure(project(':api')) { testAnnotationProcessor 'org.projectlombok:lombok:1.18.2' testCompile "com.github.javafaker:javafaker:0.14" testCompile "org.apache.commons:commons-lang3:$langVersion" + testCompile "org.arquillian.universe:arquillian-junit:1.2.0.1" + testCompile "org.arquillian.universe:arquillian-cube-docker:1.2.0.1" + testCompile "org.arquillian.cube:arquillian-cube-docker:1.15.3" + testCompile "io.rest-assured:rest-assured:3.0.2" testCompile 'io.swagger.core.v3:swagger-jaxrs2:2.0.6' //needed to generate documentation using SwaggerGenerator } + + sourceSets { + testIntegration { + java.srcDir 'src/testIntegration/java' + resources.srcDir 'src/testIntegration/resources' + compileClasspath += sourceSets.main.output + configurations.testRuntimeClasspath + runtimeClasspath += output + compileClasspath + } + } + + task testIntegration(type: Test) { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = 'Runs the integration tests.' + + maxHeapSize = '1024m' + + testClassesDir = sourceSets.testIntegration.output.classesDir + classpath = sourceSets.testIntegration.runtimeClasspath + + binResultsDir = file("$buildDir/api/integration-test-results/binary/testIntegration") + + reports { + html.destination = "$buildDir/api/reports/integration-test" + junitXml.destination = "$buildDir/api/integration-test-results" + } + + systemProperties = [ + CUBE_LOGGER_ENABLE: System.getenv('CUBE_LOGGER_ENABLE') + ] + + testLogging.showStandardStreams = true + testLogging.exceptionFormat = 'full' + + mustRunAfter tasks.test + } }