From 43cdf3db3b06fae6d1231edfec0ab8dddaaf99ab Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 3 Jul 2024 00:52:24 +0200 Subject: [PATCH] add integration tests for postgresql, mysql and mariadb using testcontainers --- .github/workflows/build.yml | 14 +++- commafeed-server/pom.xml | 49 ++++++++++++- .../CommaFeedDropwizardAppExtension.java | 68 ++++++++++++++++++- .../commafeed/integration/rest/FeedIT.java | 21 +++--- .../src/test/resources/config.test.yml | 3 + .../test/resources/docker-images.properties | 3 + 6 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 commafeed-server/src/test/resources/docker-images.properties diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3481ac92..3fda0319 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,10 +29,20 @@ jobs: distribution: "temurin" cache: "maven" - # Build + # Build & Test - name: Build with Maven - run: mvn --batch-mode --update-snapshots verify + run: mvn --batch-mode --no-transfer-progress install + - name: Run integration tests on PostgreSQL + run: TEST_DATABASE=postgresql mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify + + - name: Run integration tests on MySQL + run: TEST_DATABASE=mysql mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify + + - name: Run integration tests on MariaDB + run: TEST_DATABASE=mariadb mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify + + # Upload artifacts - name: Upload JAR uses: actions/upload-artifact@v4 if: ${{ matrix.java == '17' }} diff --git a/commafeed-server/pom.xml b/commafeed-server/pom.xml index 2cd193c8..e639f7d4 100644 --- a/commafeed-server/pom.xml +++ b/commafeed-server/pom.xml @@ -15,6 +15,14 @@ 7.0.0 6.5 2.1.0 + + 1.19.8 + + 16.2 + + 8.4 + + 11.2 @@ -31,6 +39,19 @@ commafeed + + + src/test/resources + false + + + src/test/resources + + docker-images.properties + + true + + @@ -64,7 +85,8 @@ true - ${project.build.outputDirectory}/git.properties + ${project.build.outputDirectory}/git.properties + false false @@ -86,6 +108,7 @@ *:* + module-info.class META-INF/*.SF META-INF/*.DSA META-INF/*.RSA @@ -471,5 +494,29 @@ test + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + + org.testcontainers + mysql + ${testcontainers.version} + test + + + org.testcontainers + mariadb + ${testcontainers.version} + test + diff --git a/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java b/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java index 9fba2322..332105ae 100644 --- a/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java +++ b/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java @@ -1,11 +1,21 @@ package com.commafeed; +import java.io.IOException; +import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; import javax.sql.DataSource; import org.mockserver.socket.PortFactory; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.containers.PostgreSQLContainer; import com.codahale.metrics.MetricRegistry; @@ -14,10 +24,57 @@ import io.dropwizard.testing.ResourceHelpers; import io.dropwizard.testing.junit5.DropwizardAppExtension; public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension { + private static final String TEST_DATABASE = System.getenv().getOrDefault("TEST_DATABASE", "h2"); + + private static final ConfigOverride[] CONFIG_OVERRIDES; + private static final List DROP_ALL_STATEMENTS; + static { + List overrides = new ArrayList<>(); + overrides.add(ConfigOverride.config("server.applicationConnectors[0].port", String.valueOf(PortFactory.findFreePort()))); + + DatabaseConfiguration config = buildConfiguration(TEST_DATABASE); + JdbcDatabaseContainer container = config.container(); + if (container != null) { + container.withDatabaseName("commafeed"); + container.withEnv("TZ", "UTC"); + container.start(); + + overrides.add(ConfigOverride.config("database.url", container.getJdbcUrl())); + overrides.add(ConfigOverride.config("database.user", container.getUsername())); + overrides.add(ConfigOverride.config("database.password", container.getPassword())); + overrides.add(ConfigOverride.config("database.driverClass", container.getDriverClassName())); + } + + CONFIG_OVERRIDES = overrides.toArray(new ConfigOverride[0]); + DROP_ALL_STATEMENTS = config.dropAllStatements(); + } public CommaFeedDropwizardAppExtension() { - super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"), - ConfigOverride.config("server.applicationConnectors[0].port", String.valueOf(PortFactory.findFreePort()))); + super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"), CONFIG_OVERRIDES); + } + + private static DatabaseConfiguration buildConfiguration(String databaseName) { + Properties properties = new Properties(); + try (InputStream is = CommaFeedDropwizardAppExtension.class.getResourceAsStream("/docker-images.properties")) { + properties.load(is); + } catch (IOException e) { + throw new RuntimeException("could not read docker-images.properties", e); + } + + String imageName = properties.getProperty(databaseName); + if ("postgresql".equals(databaseName)) { + JdbcDatabaseContainer container = new PostgreSQLContainer<>(imageName).withTmpFs(Map.of("/var/lib/postgresql/data", "rw")); + return new DatabaseConfiguration(container, List.of("DROP SCHEMA public CASCADE", "CREATE SCHEMA public")); + } else if ("mysql".equals(databaseName)) { + JdbcDatabaseContainer container = new MySQLContainer<>(imageName).withTmpFs(Map.of("/var/lib/mysql", "rw")); + return new DatabaseConfiguration(container, List.of("DROP DATABASE IF EXISTS commafeed", " CREATE DATABASE commafeed")); + } else if ("mariadb".equals(databaseName)) { + JdbcDatabaseContainer container = new MariaDBContainer<>(imageName).withTmpFs(Map.of("/var/lib/mysql", "rw")); + return new DatabaseConfiguration(container, List.of("DROP DATABASE IF EXISTS commafeed", " CREATE DATABASE commafeed")); + } else { + // h2 + return new DatabaseConfiguration(null, List.of("DROP ALL OBJECTS")); + } } @Override @@ -27,10 +84,15 @@ public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension container, List dropAllStatements) { + } + } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java index f8c35994..c5c6c2a5 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java @@ -112,10 +112,11 @@ class FeedIT extends BaseIT { @Test void markInsertedBeforeBeforeSubscription() { - Instant insertedBefore = Instant.now(); + // mariadb/mysql timestamp precision is 1 second + Instant threshold = Instant.now().minus(Duration.ofSeconds(1)); long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); - markFeedEntries(subscriptionId, null, insertedBefore); + markFeedEntries(subscriptionId, null, threshold); Assertions.assertTrue(getFeedEntries(subscriptionId).getEntries().stream().noneMatch(Entry::isRead)); } @@ -123,9 +124,10 @@ class FeedIT extends BaseIT { void markInsertedBeforeAfterSubscription() { long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); - Instant insertedBefore = Instant.now(); + // mariadb/mysql timestamp precision is 1 second + Instant threshold = Instant.now().plus(Duration.ofSeconds(1)); - markFeedEntries(subscriptionId, null, insertedBefore); + markFeedEntries(subscriptionId, null, threshold); Assertions.assertTrue(getFeedEntries(subscriptionId).getEntries().stream().allMatch(Entry::isRead)); } @@ -144,26 +146,29 @@ class FeedIT extends BaseIT { void refresh() { Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); - Instant now = Instant.now(); + // mariadb/mysql timestamp precision is 1 second + Instant threshold = Instant.now().minus(Duration.ofSeconds(1)); + IDRequest request = new IDRequest(); request.setId(subscriptionId); getClient().target(getApiBaseUrl() + "feed/refresh").request().post(Entity.json(request), Void.TYPE); Awaitility.await() .atMost(Duration.ofSeconds(15)) - .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(now)); + .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(threshold)); } @Test void refreshAll() { Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); - Instant now = Instant.now(); + // mariadb/mysql timestamp precision is 1 second + Instant threshold = Instant.now().minus(Duration.ofSeconds(1)); getClient().target(getApiBaseUrl() + "feed/refreshAll").request().get(Void.TYPE); Awaitility.await() .atMost(Duration.ofSeconds(15)) - .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(now)); + .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(threshold)); } } diff --git a/commafeed-server/src/test/resources/config.test.yml b/commafeed-server/src/test/resources/config.test.yml index 3768ab03..e67b9ac8 100644 --- a/commafeed-server/src/test/resources/config.test.yml +++ b/commafeed-server/src/test/resources/config.test.yml @@ -117,6 +117,9 @@ database: properties: charSet: UTF-8 validationQuery: "/* CommaFeed Health Check */ SELECT 1" + minSize: 1 + maxSize: 5 + initialSize: 1 server: applicationConnectors: diff --git a/commafeed-server/src/test/resources/docker-images.properties b/commafeed-server/src/test/resources/docker-images.properties new file mode 100644 index 00000000..c022ecd7 --- /dev/null +++ b/commafeed-server/src/test/resources/docker-images.properties @@ -0,0 +1,3 @@ +postgresql=postgres:${postgresql.image.version} +mysql=mysql:${mysql.image.version} +mariadb=mariadb:${mariadb.image.version}