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}