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
+20
View File
@@ -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 }}
+12 -2
View File
@@ -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()));
}
}
+14 -2
View File
@@ -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 {
@@ -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());
}
}
+21 -4
View File
@@ -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);
}
@@ -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());
}
}
+15 -1
View File
@@ -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 {
@@ -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());
}
}
+15 -1
View File
@@ -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 {
@@ -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()))));
}
});
}
});
}
@@ -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
View File
@@ -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')
-2
View File
@@ -1,2 +0,0 @@
systemProp.javax.net.ssl.trustStore=NONE
systemProp.javax.net.ssl.trustStoreType=Windows-ROOT
Vendored Regular → Executable
View File
+1
View File
@@ -15,3 +15,4 @@ include 'Sponge:SpongeLoader'
include 'Velocity:VelocityPlugin'
include 'Velocity:VelocityLoader'