add integration tests for postgresql, mysql and mariadb using testcontainers

This commit is contained in:
Athou
2024-07-03 00:52:24 +02:00
parent 280a354228
commit 43cdf3db3b
6 changed files with 144 additions and 14 deletions

View File

@@ -29,10 +29,20 @@ jobs:
distribution: "temurin" distribution: "temurin"
cache: "maven" cache: "maven"
# Build # Build & Test
- name: Build with Maven - 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 - name: Upload JAR
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: ${{ matrix.java == '17' }} if: ${{ matrix.java == '17' }}

View File

@@ -15,6 +15,14 @@
<guice.version>7.0.0</guice.version> <guice.version>7.0.0</guice.version>
<querydsl.version>6.5</querydsl.version> <querydsl.version>6.5</querydsl.version>
<rome.version>2.1.0</rome.version> <rome.version>2.1.0</rome.version>
<testcontainers.version>1.19.8</testcontainers.version>
<!-- renovate: datasource=docker depName=postgres -->
<postgresql.image.version>16.2</postgresql.image.version>
<!-- renovate: datasource=docker depName=mysql -->
<mysql.image.version>8.4</mysql.image.version>
<!-- renovate: datasource=docker depName=mariadb -->
<mariadb.image.version>11.2</mariadb.image.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -31,6 +39,19 @@
<build> <build>
<finalName>commafeed</finalName> <finalName>commafeed</finalName>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>false</filtering>
</testResource>
<testResource>
<directory>src/test/resources</directory>
<includes>
<include>docker-images.properties</include>
</includes>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins> <plugins>
<plugin> <plugin>
@@ -64,7 +85,8 @@
</executions> </executions>
<configuration> <configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile> <generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename> <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties
</generateGitPropertiesFilename>
<failOnNoGitDirectory>false</failOnNoGitDirectory> <failOnNoGitDirectory>false</failOnNoGitDirectory>
<failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo> <failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
</configuration> </configuration>
@@ -86,6 +108,7 @@
<filter> <filter>
<artifact>*:*</artifact> <artifact>*:*</artifact>
<excludes> <excludes>
<exclude>module-info.class</exclude>
<exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude> <exclude>META-INF/*.RSA</exclude>
@@ -471,5 +494,29 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mariadb</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,11 +1,21 @@
package com.commafeed; package com.commafeed;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; 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 javax.sql.DataSource;
import org.mockserver.socket.PortFactory; 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; import com.codahale.metrics.MetricRegistry;
@@ -14,10 +24,57 @@ import io.dropwizard.testing.ResourceHelpers;
import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardAppExtension;
public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension<CommaFeedConfiguration> { public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension<CommaFeedConfiguration> {
private static final String TEST_DATABASE = System.getenv().getOrDefault("TEST_DATABASE", "h2");
private static final ConfigOverride[] CONFIG_OVERRIDES;
private static final List<String> DROP_ALL_STATEMENTS;
static {
List<ConfigOverride> 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() { public CommaFeedDropwizardAppExtension() {
super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"), super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"), CONFIG_OVERRIDES);
ConfigOverride.config("server.applicationConnectors[0].port", String.valueOf(PortFactory.findFreePort()))); }
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 @Override
@@ -27,10 +84,15 @@ public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension<Comm
// clean database after each test // clean database after each test
DataSource dataSource = getConfiguration().getDataSourceFactory().build(new MetricRegistry(), "cleanup"); DataSource dataSource = getConfiguration().getDataSourceFactory().build(new MetricRegistry(), "cleanup");
try (Connection connection = dataSource.getConnection()) { try (Connection connection = dataSource.getConnection()) {
connection.prepareStatement("DROP ALL OBJECTS").executeUpdate(); for (String statement : DROP_ALL_STATEMENTS) {
connection.prepareStatement(statement).executeUpdate();
}
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("could not cleanup database", e); throw new RuntimeException("could not cleanup database", e);
} }
} }
private record DatabaseConfiguration(JdbcDatabaseContainer<?> container, List<String> dropAllStatements) {
}
} }

View File

@@ -112,10 +112,11 @@ class FeedIT extends BaseIT {
@Test @Test
void markInsertedBeforeBeforeSubscription() { 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()); long subscriptionId = subscribeAndWaitForEntries(getFeedUrl());
markFeedEntries(subscriptionId, null, insertedBefore); markFeedEntries(subscriptionId, null, threshold);
Assertions.assertTrue(getFeedEntries(subscriptionId).getEntries().stream().noneMatch(Entry::isRead)); Assertions.assertTrue(getFeedEntries(subscriptionId).getEntries().stream().noneMatch(Entry::isRead));
} }
@@ -123,9 +124,10 @@ class FeedIT extends BaseIT {
void markInsertedBeforeAfterSubscription() { void markInsertedBeforeAfterSubscription() {
long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); 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)); Assertions.assertTrue(getFeedEntries(subscriptionId).getEntries().stream().allMatch(Entry::isRead));
} }
@@ -144,26 +146,29 @@ class FeedIT extends BaseIT {
void refresh() { void refresh() {
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); 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(); IDRequest request = new IDRequest();
request.setId(subscriptionId); request.setId(subscriptionId);
getClient().target(getApiBaseUrl() + "feed/refresh").request().post(Entity.json(request), Void.TYPE); getClient().target(getApiBaseUrl() + "feed/refresh").request().post(Entity.json(request), Void.TYPE);
Awaitility.await() Awaitility.await()
.atMost(Duration.ofSeconds(15)) .atMost(Duration.ofSeconds(15))
.until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(now)); .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(threshold));
} }
@Test @Test
void refreshAll() { void refreshAll() {
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); 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); getClient().target(getApiBaseUrl() + "feed/refreshAll").request().get(Void.TYPE);
Awaitility.await() Awaitility.await()
.atMost(Duration.ofSeconds(15)) .atMost(Duration.ofSeconds(15))
.until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(now)); .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(threshold));
} }
} }

View File

@@ -117,6 +117,9 @@ database:
properties: properties:
charSet: UTF-8 charSet: UTF-8
validationQuery: "/* CommaFeed Health Check */ SELECT 1" validationQuery: "/* CommaFeed Health Check */ SELECT 1"
minSize: 1
maxSize: 5
initialSize: 1
server: server:
applicationConnectors: applicationConnectors:

View File

@@ -0,0 +1,3 @@
postgresql=postgres:${postgresql.image.version}
mysql=mysql:${mysql.image.version}
mariadb=mariadb:${mariadb.image.version}