mirror of
https://github.com/funkemunky/AntiVPN.git
synced 2026-05-31 01:21:55 +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:
@@ -19,6 +19,26 @@ jobs:
|
||||
with:
|
||||
gradle-version: '9.4.1'
|
||||
- name: Build
|
||||
run: gradle build -x test --no-daemon
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'zulu'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-version: '9.4.1'
|
||||
- name: Build and Test
|
||||
run: gradle build --no-daemon
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -4,9 +4,14 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT'
|
||||
compileOnly project(path: ':Common:Source', configuration: 'shadow')
|
||||
compileOnly project(':Common:loader-utils')
|
||||
implementation project(':Common:Source')
|
||||
implementation project(':Common:loader-utils')
|
||||
implementation 'org.bstats:bstats-bukkit:2.2.1'
|
||||
testImplementation 'com.github.seeseemelk:MockBukkit-v1.20:3.84.0'
|
||||
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-subclass:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
@@ -15,4 +20,9 @@ shadowJar {
|
||||
relocate 'org.yaml.snakeyaml', 'dev.brighten.antivpn.shaded.org.yaml.snakeyaml'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty 'mockito.mockmaker', 'subclass'
|
||||
}
|
||||
|
||||
tasks.build.dependsOn shadowJar
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package dev.brighten.antivpn.bukkit;
|
||||
|
||||
import be.seeseemelk.mockbukkit.MockBukkit;
|
||||
import be.seeseemelk.mockbukkit.ServerMock;
|
||||
import be.seeseemelk.mockbukkit.entity.PlayerMock;
|
||||
import dev.brighten.antivpn.AntiVPN;
|
||||
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||
import dev.brighten.antivpn.api.VPNConfig;
|
||||
import dev.brighten.antivpn.api.VPNExecutor;
|
||||
import dev.brighten.antivpn.message.MessageHandler;
|
||||
import dev.brighten.antivpn.message.VpnString;
|
||||
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class BukkitListenerTest {
|
||||
|
||||
private ServerMock server;
|
||||
private BukkitListener listener;
|
||||
private VPNExecutor vpnExecutor;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
server = MockBukkit.mock();
|
||||
|
||||
AntiVPN antiVPN = mock(AntiVPN.class);
|
||||
VPNConfig config = mock(VPNConfig.class);
|
||||
PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
|
||||
vpnExecutor = mock(VPNExecutor.class);
|
||||
MessageHandler messageHandler = mock(MessageHandler.class);
|
||||
|
||||
when(antiVPN.getVpnConfig()).thenReturn(config);
|
||||
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
|
||||
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
|
||||
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
|
||||
|
||||
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
|
||||
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.isKickPlayers()).thenReturn(true);
|
||||
when(config.getKickMessage()).thenReturn("Blocked!");
|
||||
|
||||
VpnString mockVpnString = mock(VpnString.class);
|
||||
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
|
||||
when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
|
||||
|
||||
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
|
||||
.method("N/A").countryName("N/A").city("N/A").build()
|
||||
));
|
||||
|
||||
// Use reflection to set the private static INSTANCE field
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, antiVPN);
|
||||
|
||||
listener = new BukkitListener();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
// Reset the singleton
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, null);
|
||||
|
||||
MockBukkit.unmock();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginEventAllowed() throws Exception {
|
||||
PlayerMock player = server.addPlayer("TestPlayer");
|
||||
InetAddress address = InetAddress.getByName("127.0.0.1");
|
||||
|
||||
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
|
||||
|
||||
listener.onLogin(event);
|
||||
|
||||
assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginEventBlocked() throws Exception {
|
||||
PlayerMock player = server.addPlayer("ProxyPlayer");
|
||||
InetAddress address = InetAddress.getByName("1.1.1.1");
|
||||
|
||||
// Mock proxy response
|
||||
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
|
||||
));
|
||||
|
||||
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
|
||||
|
||||
listener.onLogin(event);
|
||||
|
||||
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
|
||||
assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage()));
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,21 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
compileOnly 'net.md-5:bungeecord-api:1.21-R0.2'
|
||||
compileOnly project(path: ':Common:Source', configuration: 'shadow')
|
||||
compileOnly project(':Common:loader-utils')
|
||||
testImplementation 'net.md-5:bungeecord-api:1.21-R0.2'
|
||||
implementation project(':Common:Source')
|
||||
implementation project(':Common:loader-utils')
|
||||
implementation 'org.bstats:bstats-bungeecord:2.2.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-subclass:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
}
|
||||
tasks.compileJava.dependsOn(':Common:Source:jar')
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty 'mockito.mockmaker', 'subclass'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package dev.brighten.antivpn.bungee;
|
||||
|
||||
import dev.brighten.antivpn.AntiVPN;
|
||||
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||
import dev.brighten.antivpn.api.VPNConfig;
|
||||
import dev.brighten.antivpn.api.VPNExecutor;
|
||||
import dev.brighten.antivpn.message.MessageHandler;
|
||||
import dev.brighten.antivpn.message.VpnString;
|
||||
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class BungeeListenerTest {
|
||||
|
||||
private BungeeListener listener;
|
||||
private VPNExecutor vpnExecutor;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
AntiVPN antiVPN = mock(AntiVPN.class);
|
||||
VPNConfig config = mock(VPNConfig.class);
|
||||
PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
|
||||
vpnExecutor = mock(VPNExecutor.class);
|
||||
MessageHandler messageHandler = mock(MessageHandler.class);
|
||||
|
||||
when(antiVPN.getVpnConfig()).thenReturn(config);
|
||||
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
|
||||
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
|
||||
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
|
||||
|
||||
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
|
||||
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.isKickPlayers()).thenReturn(true);
|
||||
when(config.getKickMessage()).thenReturn("Blocked!");
|
||||
|
||||
VpnString mockVpnString = mock(VpnString.class);
|
||||
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
|
||||
when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
|
||||
|
||||
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
|
||||
.method("N/A").countryName("N/A").city("N/A").build()
|
||||
));
|
||||
|
||||
// Use reflection to set the private static INSTANCE field
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, antiVPN);
|
||||
|
||||
listener = new BungeeListener();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
// Reset the singleton
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreLoginEventAllowed() {
|
||||
PreLoginEvent event = mock(PreLoginEvent.class);
|
||||
PendingConnection connection = mock(PendingConnection.class);
|
||||
|
||||
when(event.getConnection()).thenReturn(connection);
|
||||
when(connection.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
when(connection.getName()).thenReturn("TestPlayer");
|
||||
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345));
|
||||
|
||||
listener.onListener(event);
|
||||
|
||||
verify(event, never()).setCancelled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreLoginEventBlocked() {
|
||||
PreLoginEvent event = mock(PreLoginEvent.class);
|
||||
PendingConnection connection = mock(PendingConnection.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
when(event.getConnection()).thenReturn(connection);
|
||||
when(connection.getUniqueId()).thenReturn(uuid);
|
||||
when(connection.getName()).thenReturn("ProxyPlayer");
|
||||
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
|
||||
|
||||
// Mock proxy response
|
||||
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
|
||||
));
|
||||
|
||||
listener.onListener(event);
|
||||
|
||||
verify(event).setCancelled(true);
|
||||
verify(event).setReason(any());
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,18 @@ dependencies {
|
||||
compileOnly 'com.h2database:h2:2.2.220'
|
||||
compileOnly 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
compileOnly 'org.mongodb:mongo-java-driver:3.12.14'
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
|
||||
testImplementation "org.testcontainers:testcontainers:2.0.4"
|
||||
testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.4"
|
||||
testImplementation 'org.testcontainers:mysql:1.20.4'
|
||||
testImplementation 'org.testcontainers:mongodb:1.20.4'
|
||||
testRuntimeOnly 'org.slf4j:slf4j-simple:2.0.16'
|
||||
testImplementation 'com.mysql:mysql-connector-j:9.3.0'
|
||||
testImplementation 'com.h2database:h2:2.2.220'
|
||||
testImplementation 'org.mongodb:mongo-java-driver:3.12.14'
|
||||
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
@@ -38,3 +46,12 @@ tasks.build.dependsOn shadowJar
|
||||
components.java.withVariantsFromConfiguration(configurations.shadowRuntimeElements) {
|
||||
skip()
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar {
|
||||
archiveClassifier.set('raw')
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,22 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.spongepowered:spongeapi:11.0.0'
|
||||
compileOnly project(path: ':Common:Source', configuration: 'shadow')
|
||||
testImplementation 'org.spongepowered:spongeapi:11.0.0'
|
||||
compileOnly project(':Common:Source')
|
||||
testImplementation project(':Common:Source')
|
||||
compileOnly project(':Common:loader-utils')
|
||||
testImplementation project(':Common:loader-utils')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-subclass:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
}
|
||||
tasks.compileJava.dependsOn(':Common:Source:jar')
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty 'mockito.mockmaker', 'subclass'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package dev.brighten.antivpn.sponge;
|
||||
|
||||
import dev.brighten.antivpn.AntiVPN;
|
||||
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||
import dev.brighten.antivpn.api.VPNConfig;
|
||||
import dev.brighten.antivpn.api.VPNExecutor;
|
||||
import dev.brighten.antivpn.message.MessageHandler;
|
||||
import dev.brighten.antivpn.message.VpnString;
|
||||
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
|
||||
import org.spongepowered.api.network.ServerSideConnection;
|
||||
import org.spongepowered.api.profile.GameProfile;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class SpongeListenerTest {
|
||||
|
||||
private SpongeListener listener;
|
||||
private VPNExecutor vpnExecutor;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
AntiVPN antiVPN = mock(AntiVPN.class);
|
||||
VPNConfig config = mock(VPNConfig.class);
|
||||
PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
|
||||
vpnExecutor = mock(VPNExecutor.class);
|
||||
MessageHandler messageHandler = mock(MessageHandler.class);
|
||||
|
||||
when(antiVPN.getVpnConfig()).thenReturn(config);
|
||||
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
|
||||
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
|
||||
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
|
||||
|
||||
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
|
||||
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.isKickPlayers()).thenReturn(true);
|
||||
when(config.getKickMessage()).thenReturn("Blocked!");
|
||||
|
||||
VpnString mockVpnString = mock(VpnString.class);
|
||||
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
|
||||
when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
|
||||
|
||||
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
|
||||
.method("N/A").countryName("N/A").city("N/A").build()
|
||||
));
|
||||
|
||||
// Use reflection to set the private static INSTANCE field
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, antiVPN);
|
||||
|
||||
listener = new SpongeListener();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
// Reset the singleton
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginEventAllowed() {
|
||||
ServerSideConnectionEvent.Login event = mock(ServerSideConnectionEvent.Login.class);
|
||||
GameProfile profile = mock(GameProfile.class);
|
||||
ServerSideConnection connection = mock(ServerSideConnection.class);
|
||||
|
||||
when(event.profile()).thenReturn(profile);
|
||||
when(event.connection()).thenReturn(connection);
|
||||
when(profile.uuid()).thenReturn(UUID.randomUUID());
|
||||
when(profile.name()).thenReturn(Optional.of("TestPlayer"));
|
||||
when(connection.address()).thenReturn(new InetSocketAddress("127.0.0.1", 12345));
|
||||
|
||||
listener.onJoin(event);
|
||||
|
||||
verify(event, never()).setCancelled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginEventBlocked() {
|
||||
ServerSideConnectionEvent.Login event = mock(ServerSideConnectionEvent.Login.class);
|
||||
GameProfile profile = mock(GameProfile.class);
|
||||
ServerSideConnection connection = mock(ServerSideConnection.class);
|
||||
|
||||
when(event.profile()).thenReturn(profile);
|
||||
when(event.connection()).thenReturn(connection);
|
||||
when(profile.uuid()).thenReturn(UUID.randomUUID());
|
||||
when(profile.name()).thenReturn(Optional.of("ProxyPlayer"));
|
||||
when(connection.address()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
|
||||
|
||||
// Mock proxy response
|
||||
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
|
||||
));
|
||||
|
||||
listener.onJoin(event);
|
||||
|
||||
verify(event).setCancelled(true);
|
||||
verify(event).setMessage(any());
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,23 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.velocitypowered:velocity-api:3.4.0-SNAPSHOT'
|
||||
compileOnly project(path: ':Common:Source', configuration: 'shadow')
|
||||
testImplementation 'com.velocitypowered:velocity-api:3.4.0-SNAPSHOT'
|
||||
compileOnly project(':Common:Source')
|
||||
compileOnly project(':Common:loader-utils')
|
||||
implementation 'org.bstats:bstats-velocity:2.2.1'
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||
testImplementation 'net.java.dev.jna:jna:5.14.0'
|
||||
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
|
||||
testImplementation project(':Common:Source')
|
||||
testImplementation project(':Common:loader-utils')
|
||||
}
|
||||
tasks.compileJava.dependsOn(':Common:Source:jar')
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
||||
+30
-28
@@ -41,43 +41,45 @@ public class VelocityListener extends VPNExecutor {
|
||||
.unloadPlayer(event.getPlayer().getUniqueId()));
|
||||
|
||||
VelocityPlugin.INSTANCE.getServer().getEventManager().register(VelocityPlugin.INSTANCE.getPluginInstance(), LoginEvent.class,
|
||||
event -> {
|
||||
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
|
||||
.orElse(new OfflinePlayer(
|
||||
event.getPlayer().getUniqueId(),
|
||||
event.getPlayer().getUsername(),
|
||||
event.getPlayer().getRemoteAddress().getAddress()
|
||||
));
|
||||
this::onLogin);
|
||||
}
|
||||
|
||||
player.checkPlayer(result -> {
|
||||
if(!result.resultType().isShouldBlock()) return;
|
||||
public void onLogin(LoginEvent event) {
|
||||
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
|
||||
.orElse(new OfflinePlayer(
|
||||
event.getPlayer().getUniqueId(),
|
||||
event.getPlayer().getUsername(),
|
||||
event.getPlayer().getRemoteAddress().getAddress()
|
||||
));
|
||||
|
||||
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
|
||||
return;
|
||||
}
|
||||
player.checkPlayer(result -> {
|
||||
if(!result.resultType().isShouldBlock()) return;
|
||||
|
||||
switch (result.resultType()) {
|
||||
case DENIED_COUNTRY -> event.setResult(ResultedEvent.ComponentResult.denied(
|
||||
LegacyComponentSerializer.builder()
|
||||
.character('&')
|
||||
.build().deserialize(AntiVPN.getInstance().getVpnConfig()
|
||||
.getCountryVanillaKickReason()
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%country%", result.response().getCountryName())
|
||||
.replace("%code%", result.response().getCountryCode()))));
|
||||
case DENIED_PROXY -> {
|
||||
VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername()
|
||||
+ " joined on a VPN/Proxy (" + result.response().getMethod() + ")");
|
||||
event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder()
|
||||
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (result.resultType()) {
|
||||
case DENIED_COUNTRY -> event.setResult(ResultedEvent.ComponentResult.denied(
|
||||
LegacyComponentSerializer.builder()
|
||||
.character('&')
|
||||
.build().deserialize(AntiVPN.getInstance().getVpnConfig()
|
||||
.getKickMessage()
|
||||
.getCountryVanillaKickReason()
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%country%", result.response().getCountryName())
|
||||
.replace("%code%", result.response().getCountryCode()))));
|
||||
}
|
||||
case DENIED_PROXY -> {
|
||||
VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername()
|
||||
+ " joined on a VPN/Proxy (" + result.response().getMethod() + ")");
|
||||
event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder()
|
||||
.character('&')
|
||||
.build().deserialize(AntiVPN.getInstance().getVpnConfig()
|
||||
.getKickMessage()
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%country%", result.response().getCountryName())
|
||||
.replace("%code%", result.response().getCountryCode()))));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package dev.brighten.antivpn.velocity;
|
||||
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import dev.brighten.antivpn.AntiVPN;
|
||||
import dev.brighten.antivpn.api.PlayerExecutor;
|
||||
import dev.brighten.antivpn.api.VPNConfig;
|
||||
import dev.brighten.antivpn.api.VPNExecutor;
|
||||
import dev.brighten.antivpn.message.MessageHandler;
|
||||
import dev.brighten.antivpn.message.VpnString;
|
||||
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class VelocityListenerTest {
|
||||
|
||||
private VelocityListener listener;
|
||||
private AntiVPN antiVPN;
|
||||
private VPNConfig config;
|
||||
private PlayerExecutor playerExecutor;
|
||||
private VPNExecutor vpnExecutor;
|
||||
private MessageHandler messageHandler;
|
||||
private VelocityPlugin velocityPlugin;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
antiVPN = mock(AntiVPN.class);
|
||||
config = mock(VPNConfig.class);
|
||||
playerExecutor = mock(PlayerExecutor.class);
|
||||
vpnExecutor = mock(VPNExecutor.class);
|
||||
messageHandler = mock(MessageHandler.class);
|
||||
velocityPlugin = mock(VelocityPlugin.class);
|
||||
|
||||
when(antiVPN.getVpnConfig()).thenReturn(config);
|
||||
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
|
||||
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
|
||||
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
|
||||
|
||||
when(velocityPlugin.getLogger()).thenReturn(Logger.getLogger("AntiVPN"));
|
||||
|
||||
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
|
||||
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
|
||||
when(config.isKickPlayers()).thenReturn(true);
|
||||
when(config.getKickMessage()).thenReturn("Blocked!");
|
||||
|
||||
VpnString mockVpnString = mock(VpnString.class);
|
||||
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
|
||||
when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
|
||||
|
||||
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
|
||||
.method("N/A").countryName("N/A").city("N/A").countryCode("N/A").build()
|
||||
));
|
||||
|
||||
// Use reflection to set the private static INSTANCE field
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, antiVPN);
|
||||
|
||||
Field pluginInstanceField = VelocityPlugin.class.getDeclaredField("INSTANCE");
|
||||
pluginInstanceField.setAccessible(true);
|
||||
pluginInstanceField.set(null, velocityPlugin);
|
||||
|
||||
listener = new VelocityListener();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
// Reset the singletons
|
||||
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, null);
|
||||
|
||||
Field pluginInstanceField = VelocityPlugin.class.getDeclaredField("INSTANCE");
|
||||
pluginInstanceField.setAccessible(true);
|
||||
pluginInstanceField.set(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginEventAllowed() throws Exception {
|
||||
LoginEvent event = mock(LoginEvent.class);
|
||||
Player player = mock(Player.class);
|
||||
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
when(player.getUsername()).thenReturn("TestPlayer");
|
||||
when(player.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345));
|
||||
|
||||
listener.onLogin(event);
|
||||
|
||||
verify(event, never()).setResult(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginEventBlocked() throws Exception {
|
||||
LoginEvent event = mock(LoginEvent.class);
|
||||
Player player = mock(Player.class);
|
||||
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
when(player.getUsername()).thenReturn("ProxyPlayer");
|
||||
when(player.getRemoteAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
|
||||
|
||||
// Mock proxy response
|
||||
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
|
||||
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
|
||||
.method("N/A").countryName("N/A").city("N/A").countryCode("N/A").build()
|
||||
));
|
||||
|
||||
listener.onLogin(event);
|
||||
|
||||
verify(event).setResult(any());
|
||||
}
|
||||
}
|
||||
+35
-2
@@ -3,14 +3,24 @@ plugins {
|
||||
id 'com.gradleup.shadow' version '9.4.1'
|
||||
}
|
||||
|
||||
def aggregateTestProjects = [
|
||||
project(':Common:Source'),
|
||||
project(':Bukkit:Plugin'),
|
||||
project(':Bungee:BungeePlugin'),
|
||||
project(':Sponge:SpongePlugin'),
|
||||
project(':Velocity:VelocityPlugin')
|
||||
]
|
||||
|
||||
allprojects {
|
||||
group = 'dev.brighten.antivpn'
|
||||
version = '1.10.0'
|
||||
|
||||
repositories {
|
||||
maven {url 'https://nexus.funkemunky.cc/repository/maven-central/'}
|
||||
maven { url 'https://nexus.funkemunky.cc/content/repositories/releases/' }
|
||||
maven { url 'https://repo.papermc.io/repository/maven-public/' }
|
||||
maven { url 'https://nexus.funkemunky.cc/repository/papermc-public/' }
|
||||
maven { url 'https://nexus.funkemunky.cc/repository/maven-public/' }
|
||||
maven { url 'https://nexus.funkemunky.cc/repository/maven-central/' }
|
||||
maven { url 'https://nexus.funkemunky.cc/content/repositories/releases/' }
|
||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
||||
maven { url 'https://repo.spongepowered.org/repository/maven-public/' }
|
||||
@@ -33,7 +43,30 @@ allprojects {
|
||||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.44'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.44'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty 'mockito.mockmaker', 'subclass'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.junit.platform:junit-platform-suite:1.11.4'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
test {
|
||||
compileClasspath += files(aggregateTestProjects.collect { it.sourceSets.test.output + it.sourceSets.test.compileClasspath })
|
||||
runtimeClasspath += files(aggregateTestProjects.collect { it.sourceSets.test.output + it.sourceSets.test.runtimeClasspath })
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
dependsOn(aggregateTestProjects.collect { it.tasks.named('testClasses') })
|
||||
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
||||
}
|
||||
|
||||
evaluationDependsOn(':Common:Source')
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
systemProp.javax.net.ssl.trustStore=NONE
|
||||
systemProp.javax.net.ssl.trustStoreType=Windows-ROOT
|
||||
|
||||
@@ -15,3 +15,4 @@ include 'Sponge:SpongeLoader'
|
||||
include 'Velocity:VelocityPlugin'
|
||||
include 'Velocity:VelocityLoader'
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user