forked from Archives/Athou_commafeed
add integration tests for postgresql, mysql and mariadb using testcontainers
This commit is contained in:
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -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' }}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
postgresql=postgres:${postgresql.image.version}
|
||||||
|
mysql=mysql:${mysql.image.version}
|
||||||
|
mariadb=mariadb:${mariadb.image.version}
|
||||||
Reference in New Issue
Block a user