mirror of
https://github.com/funkemunky/AntiVPN.git
synced 2026-06-16 08:30:38 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
+217
@@ -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() {}
|
||||
}
|
||||
}
|
||||
+12
@@ -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());
|
||||
}
|
||||
}
|
||||
+40
@@ -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());
|
||||
}
|
||||
}
|
||||
+45
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user