Added automated testing (#82)

* Using built in pipeline

* Added testing for databases, and fixed some potential database bugs

* Fixing MySQL issues

* Adding central test suite

* Updating test workflow to build without tests and one with tests
This commit is contained in:
Dawson
2026-04-08 21:29:37 -04:00
committed by GitHub
parent 57109e4c97
commit 1acdfe8c4b
26 changed files with 1035 additions and 128 deletions
@@ -206,8 +206,12 @@ public class AntiVPN {
executor.log("Failed to deregister H2 driver: " + e.getMessage());
}
}
AntiVPN.getInstance().getExecutor().getThreadExecutor().shutdown();
if (executor != null && executor.getThreadExecutor() != null) {
executor.getThreadExecutor().shutdown();
}
if(database != null) database.shutdown();
INSTANCE = null;
}
public void reloadDatabase() {
@@ -16,6 +16,8 @@
package dev.brighten.antivpn.api;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.utils.StringUtil;
@@ -146,7 +148,18 @@ public abstract class VPNExecutor {
}
}
private final Cache<String, VPNResponse> cachedResponses = Caffeine.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.maximumSize(4000)
.build();
public CompletableFuture<VPNResponse> checkIp(String ip) {
VPNResponse cached = cachedResponses.getIfPresent(ip);
if(cached != null) {
return CompletableFuture.completedFuture(cached);
}
return CompletableFuture.supplyAsync(() -> {
Optional<VPNResponse> cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip);
@@ -16,8 +16,6 @@
package dev.brighten.antivpn.database.local;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.database.VPNDatabase;
import dev.brighten.antivpn.database.sql.utils.ExecutableStatement;
@@ -40,11 +38,6 @@ import java.util.function.Consumer;
public class H2VPN implements VPNDatabase {
private final Cache<String, VPNResponse> cachedResponses = Caffeine.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.maximumSize(4000)
.build();
public H2VPN() {
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
@@ -67,29 +60,25 @@ public class H2VPN implements VPNDatabase {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()|| MySQL.isClosed())
return Optional.empty();
VPNResponse response = cachedResponses.get(ip, ip2 -> {
try(ExecutableStatement statement = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) {
try(ResultSet rs = statement.executeQuery()) {
if (rs != null && rs.next()) {
return new VPNResponse(rs.getString("asn"), rs.getString("ip"),
rs.getString("countryName"), rs.getString("countryCode"),
rs.getString("city"), rs.getString("timeZone"),
rs.getString("method"), rs.getString("isp"), "N/A",
rs.getBoolean("proxy"), rs.getBoolean("cached"), true,
rs.getDouble("latitude"), rs.getDouble("longitude"),
rs.getTimestamp("inserted").getTime(), -1);
}
try(ExecutableStatement statement = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) {
try(ResultSet rs = statement.executeQuery()) {
if (rs != null && rs.next()) {
return Optional.of(new VPNResponse(rs.getString("asn"), rs.getString("ip"),
rs.getString("countryName"), rs.getString("countryCode"),
rs.getString("city"), rs.getString("timeZone"),
rs.getString("method"), rs.getString("isp"), "N/A",
rs.getBoolean("proxy"), rs.getBoolean("cached"), true,
rs.getDouble("latitude"), rs.getDouble("longitude"),
rs.getTimestamp("inserted").getTime(), -1));
}
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for "
+ ip, e);
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
});
return Optional.ofNullable(response);
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for "
+ ip, e);
} catch (Exception e) {
throw new RuntimeException(e);
}
return Optional.empty();
}
/*
@@ -106,20 +95,16 @@ public class H2VPN implements VPNDatabase {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
if(AntiVPN.getInstance().getVpnConfig().cachedResults()) {
cachedResponses.put(toCache.getIp(), toCache);
try(var statement = Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName())
.append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone())
.append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy())
.append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis()))
.append(toCache.getLatitude()).append(toCache.getLongitude())) {
statement.execute();
} catch(SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not cache response for IP: " + toCache.getIp(), e);
}
try(var statement = Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName())
.append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone())
.append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy())
.append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis()))
.append(toCache.getLatitude()).append(toCache.getLongitude())) {
statement.execute();
} catch(SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not cache response for IP: " + toCache.getIp(), e);
}
}
@@ -139,6 +124,7 @@ public class H2VPN implements VPNDatabase {
public boolean isWhitelisted(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return false;
try(var statement = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
.append(uuid.toString())) {
try(var set = statement.executeQuery()) {
@@ -52,11 +52,11 @@ public class First implements Version<VPNDatabase> {
.append(versionNumber())).execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes...");
createIndexIfAbsent("whitelisted", "uuid_1", "`uuid`");
createIndexIfAbsent("responses", "ip_1", "`ip`");
createIndexIfAbsent("responses", "proxy_1", "`proxy`");
createIndexIfAbsent("responses", "inserted_1", "`inserted`");
createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`");
createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`");
createIndexIfAbsent("responses", "responses_ip_1", "`ip`");
createIndexIfAbsent("responses", "responses_proxy_1", "`proxy`");
createIndexIfAbsent("responses", "responses_inserted_1", "`inserted`");
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
} catch (SQLException e) {
throw new DatabaseException("Failed to update database", e);
} finally {
@@ -86,6 +86,7 @@ public class Second extends First implements Version<VPNDatabase> {
}
dropIndexIfPresent("whitelisted-ips", "ip_1");
dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1");
closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute();
closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute();
} catch (Throwable e) {
@@ -123,7 +124,7 @@ public class Second extends First implements Version<VPNDatabase> {
statement.execute();
}
createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`");
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) {
statement.execute();
@@ -47,11 +47,6 @@ public class MongoVPN implements VPNDatabase {
private MongoClient client;
public MongoDatabase antivpnDatabase;
private final Cache<String, VPNResponse> cachedResponses = Caffeine.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.maximumSize(4000)
.build();
public MongoVPN() {
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
@@ -69,37 +64,32 @@ public class MongoVPN implements VPNDatabase {
}
@Override
public Optional<VPNResponse> getStoredResponse(String ip) {
VPNResponse response = cachedResponses.get(ip, ip2 -> {
Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first();
Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first();
if(rdoc != null) {
long lastUpdate = rdoc.get("lastAccess", 0L);
if(rdoc != null) {
long lastUpdate = rdoc.get("lastAccess", 0L);
if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip));
return null;
}
return VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip)
.countryName(rdoc.getString("countryName"))
.countryCode(rdoc.getString("countryCode"))
.city(rdoc.getString("city"))
.isp(rdoc.getString("isp"))
.method(rdoc.getString("method"))
.timeZone(rdoc.getString("timeZone"))
.proxy(rdoc.getBoolean("proxy"))
.cached(rdoc.getBoolean("cached"))
.success(true)
.latitude(rdoc.getDouble("latitude"))
.longitude(rdoc.getDouble("longitude"))
.lastAccess(rdoc.get("lastAccess", 0L))
.build();
if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip));
return null;
}
return null;
});
return Optional.ofNullable(response);
return Optional.of(VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip)
.countryName(rdoc.getString("countryName"))
.countryCode(rdoc.getString("countryCode"))
.city(rdoc.getString("city"))
.isp(rdoc.getString("isp"))
.method(rdoc.getString("method"))
.timeZone(rdoc.getString("timeZone"))
.proxy(rdoc.getBoolean("proxy"))
.cached(rdoc.getBoolean("cached"))
.success(true)
.latitude(rdoc.getDouble("latitude"))
.longitude(rdoc.getDouble("longitude"))
.lastAccess(rdoc.get("lastAccess", 0L))
.build());
}
return Optional.empty();
}
@Override
@@ -121,8 +111,6 @@ public class MongoVPN implements VPNDatabase {
rdoc.put("longitude", toCache.getLongitude());
rdoc.put("lastAccess", System.currentTimeMillis());
cachedResponses.put(toCache.getIp(), toCache);
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
Bson update = new Document("$set", rdoc);
cacheDocument.updateOne(Filters.eq("ip", toCache.getIp()), update,
@@ -26,6 +26,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.util.Properties;
import java.util.logging.Level;
@@ -48,9 +49,21 @@ public class MySQL {
}
conn.setAutoCommit(true);
Query.use(conn);
Query.prepare("CREATE DATABASE IF NOT EXISTS `"
+ AntiVPN.getInstance().getVpnConfig().getDatabaseName() + "`").execute();
Query.prepare("USE `" + AntiVPN.getInstance().getVpnConfig().getDatabaseName() + "`").execute();
String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName();
try {
Query.prepare("CREATE DATABASE IF NOT EXISTS `" + databaseName + "`").execute();
} catch (SQLException ex) {
if (!isDatabaseCreationPermissionIssue(ex)) {
throw ex;
}
AntiVPN.getInstance().getExecutor().log(
"No permission to create MySQL database `" + databaseName
+ "`. Attempting to use the existing database instead.");
}
Query.prepare("USE `" + databaseName + "`").execute();
AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established.");
}
} catch (Exception e) {
@@ -59,6 +72,12 @@ public class MySQL {
}
}
private static boolean isDatabaseCreationPermissionIssue(SQLException ex) {
return ex instanceof SQLSyntaxErrorException
&& ex.getMessage() != null
&& ex.getMessage().contains("Access denied");
}
public static void initH2() {
initH2(true);
}
@@ -0,0 +1,217 @@
package dev.brighten.antivpn.database;
import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.VPNConfig;
import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.database.sql.utils.MySQL;
import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.web.objects.VPNResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
abstract class DatabaseIntegrationTestSupport {
@TempDir
Path pluginFolder;
@Mock
protected AntiVPN antiVPN;
@Mock
protected VPNConfig vpnConfig;
protected TestVPNExecutor vpnExecutor;
private AutoCloseable mocks;
private final AtomicReference<VPNDatabase> activeDatabase = new AtomicReference<>();
@BeforeEach
void setUpBase() throws Exception {
mocks = MockitoAnnotations.openMocks(this);
vpnExecutor = new TestVPNExecutor();
when(antiVPN.getVpnConfig()).thenReturn(vpnConfig);
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
when(antiVPN.getPluginFolder()).thenReturn(pluginFolder.toFile());
when(antiVPN.getDatabase()).thenAnswer(invocation -> activeDatabase.get());
lenient().when(vpnConfig.isDatabaseEnabled()).thenReturn(true);
lenient().when(vpnConfig.cachedResults()).thenReturn(true);
lenient().when(vpnConfig.getUsername()).thenReturn("testuser");
lenient().when(vpnConfig.getPassword()).thenReturn("testpass");
lenient().when(vpnConfig.getDatabaseName()).thenReturn("antivpn");
lenient().when(vpnConfig.getIp()).thenReturn("127.0.0.1");
lenient().when(vpnConfig.getPort()).thenReturn(-1);
lenient().when(vpnConfig.mongoDatabaseURL()).thenReturn("");
lenient().when(vpnConfig.useDatabaseCreds()).thenReturn(false);
setAntiVpnInstance(antiVPN);
}
@AfterEach
void tearDownBase() throws Exception {
VPNDatabase database = activeDatabase.getAndSet(null);
if (database != null) {
database.shutdown();
}
MySQL.shutdown();
if (vpnExecutor != null) {
vpnExecutor.getThreadExecutor().shutdownNow();
vpnExecutor.getThreadExecutor().awaitTermination(5, TimeUnit.SECONDS);
}
setAntiVpnInstance(null);
if (mocks != null) {
mocks.close();
}
}
protected void registerDatabase(VPNDatabase database) {
activeDatabase.set(database);
}
protected void assertDatabaseContract(VPNDatabase database) throws Exception {
registerDatabase(database);
database.init();
VPNResponse response = VPNResponse.builder()
.ip("1.2.3.4")
.asn("AS123")
.countryName("United States")
.countryCode("US")
.city("New York")
.proxy(true)
.cached(true)
.success(true)
.build();
database.cacheResponse(response);
Optional<VPNResponse> storedResponse = awaitStoredResponse(database, response.getIp());
assertTrue(storedResponse.isPresent(), "Expected cached response to be stored");
assertEquals("AS123", storedResponse.get().getAsn());
assertTrue(storedResponse.get().isProxy());
database.deleteResponse(response.getIp());
awaitCondition(() -> database.getStoredResponse(response.getIp()).isEmpty(),
"Expected cached response to be deleted");
database.cacheResponse(response);
awaitCondition(() -> database.getStoredResponse(response.getIp()).isPresent(),
"Expected cached response to be restored");
UUID uuid = UUID.randomUUID();
assertFalse(database.isWhitelisted(uuid));
database.addWhitelist(uuid);
awaitCondition(() -> database.isWhitelisted(uuid), "Expected UUID whitelist entry to exist");
List<UUID> whitelisted = database.getAllWhitelisted();
assertTrue(whitelisted.contains(uuid));
database.removeWhitelist(uuid);
awaitCondition(() -> !database.isWhitelisted(uuid), "Expected UUID whitelist entry to be removed");
CIDRUtils cidr = new CIDRUtils("192.168.1.0/24");
assertFalse(database.isWhitelisted(cidr));
database.addWhitelist(cidr);
awaitCondition(() -> database.isWhitelisted(cidr), "Expected CIDR whitelist entry to exist");
List<CIDRUtils> whitelistedIps = database.getAllWhitelistedIps();
assertTrue(whitelistedIps.stream().anyMatch(entry -> entry.getCidr().equals(cidr.getCidr())));
database.removeWhitelist(cidr);
awaitCondition(() -> !database.isWhitelisted(cidr), "Expected CIDR whitelist entry to be removed");
database.updateAlertsState(uuid, true);
awaitCondition(() -> awaitAlertsState(database, uuid), "Expected alerts to be enabled");
database.updateAlertsState(uuid, false);
awaitCondition(() -> !awaitAlertsState(database, uuid), "Expected alerts to be disabled");
database.clearResponses();
awaitCondition(() -> database.getStoredResponse(response.getIp()).isEmpty(),
"Expected cached responses to be cleared");
}
private Optional<VPNResponse> awaitStoredResponse(VPNDatabase database, String ip) throws InterruptedException {
AtomicReference<Optional<VPNResponse>> result = new AtomicReference<>(Optional.empty());
awaitCondition(() -> {
Optional<VPNResponse> response = database.getStoredResponse(ip);
result.set(response);
return response.isPresent();
}, "Timed out waiting for cached response");
return result.get();
}
private boolean awaitAlertsState(VPNDatabase database, UUID uuid) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Boolean> result = new AtomicReference<>(false);
database.alertsState(uuid, enabled -> {
result.set(enabled);
latch.countDown();
});
assertTrue(latch.await(2, TimeUnit.SECONDS), "Timed out waiting for alerts state callback");
return result.get();
}
protected void awaitCondition(CheckedBooleanSupplier condition, String failureMessage) throws InterruptedException {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (System.nanoTime() < deadline) {
try {
if (condition.getAsBoolean()) {
return;
}
} catch (Exception e) {
fail(e.getMessage(), e);
return;
}
Thread.sleep(100);
}
fail(failureMessage);
}
private static void setAntiVpnInstance(AntiVPN instance) throws Exception {
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
instanceField.set(null, instance);
}
@FunctionalInterface
protected interface CheckedBooleanSupplier {
boolean getAsBoolean() throws Exception;
}
protected static final class TestVPNExecutor extends VPNExecutor {
@Override
public void registerListeners() {}
@Override
public void log(Level level, String log, Object... objects) {}
@Override
public void log(String log, Object... objects) {}
@Override
public void logException(String message, Throwable ex) {}
@Override
public void runCommand(String command) {}
@Override
public void disablePlugin() {}
}
}
@@ -0,0 +1,12 @@
package dev.brighten.antivpn.database;
import dev.brighten.antivpn.database.local.H2VPN;
import org.junit.jupiter.api.Test;
class H2DatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
@Test
void h2DatabaseImplementsTheVpnDatabaseContract() throws Exception {
assertDatabaseContract(new H2VPN());
}
}
@@ -0,0 +1,40 @@
package dev.brighten.antivpn.database;
import com.mongodb.client.MongoClients;
import dev.brighten.antivpn.database.mongo.MongoVPN;
import org.junit.jupiter.api.Test;
import org.bson.Document;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
@Testcontainers
class MongoDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
@Container
private static final MongoDBContainer MONGO = new MongoDBContainer("mongo:6.0.14");
@Test
void mongoDatabaseImplementsTheVpnDatabaseContract() throws Exception {
assertTrue(MONGO.isRunning(), "Mongo Testcontainer should be running");
try (var client = MongoClients.create(MONGO.getConnectionString())) {
var response = client.getDatabase("admin").runCommand(new Document("ping", 1));
assertEquals(1.0d, response.getDouble("ok"), "Expected Mongo container to respond to ping");
}
when(vpnConfig.getIp()).thenReturn(MONGO.getHost());
when(vpnConfig.getPort()).thenReturn(MONGO.getMappedPort(27017));
when(vpnConfig.getDatabaseName()).thenReturn("antivpn_" + UUID.randomUUID().toString().replace("-", ""));
when(vpnConfig.mongoDatabaseURL()).thenReturn("");
when(vpnConfig.useDatabaseCreds()).thenReturn(false);
assertDatabaseContract(new MongoVPN());
}
}
@@ -0,0 +1,45 @@
package dev.brighten.antivpn.database;
import dev.brighten.antivpn.database.sql.MySqlVPN;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.sql.DriverManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
@Testcontainers
class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
@Container
private static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.0.36")
.withDatabaseName("antivpn")
.withUsername("testuser")
.withPassword("testpass");
@Test
void mysqlDatabaseImplementsTheVpnDatabaseContract() throws Exception {
assertTrue(MYSQL.isRunning(), "MySQL Testcontainer should be running");
try (var connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(), MYSQL.getPassword());
var statement = connection.createStatement();
var resultSet = statement.executeQuery("SELECT 1")) {
assertTrue(resultSet.next(), "Expected a row from the MySQL container");
assertEquals(1, resultSet.getInt(1), "Expected MySQL container to respond to SELECT 1");
}
var pingResult = MYSQL.execInContainer("mysqladmin", "ping", "-h", "127.0.0.1", "-ptestpass");
assertEquals(0, pingResult.getExitCode(), "Expected mysqladmin ping to succeed inside the container");
when(vpnConfig.getIp()).thenReturn(MYSQL.getHost());
when(vpnConfig.getPort()).thenReturn(MYSQL.getMappedPort(3306));
when(vpnConfig.getDatabaseName()).thenReturn(MYSQL.getDatabaseName());
when(vpnConfig.getUsername()).thenReturn(MYSQL.getUsername());
when(vpnConfig.getPassword()).thenReturn(MYSQL.getPassword());
assertDatabaseContract(new MySqlVPN());
}
}